From f7647db7f7d0b9b48fefbc5cd56b9246289b680b Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 4 Oct 2016 06:30:05 -0700 Subject: [PATCH 001/176] Fix tests changed by #11309 --- .../reference/variableDeclarationInStrictMode1.errors.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/baselines/reference/variableDeclarationInStrictMode1.errors.txt b/tests/baselines/reference/variableDeclarationInStrictMode1.errors.txt index 9dd9a8d41a1..46782aaa1d6 100644 --- a/tests/baselines/reference/variableDeclarationInStrictMode1.errors.txt +++ b/tests/baselines/reference/variableDeclarationInStrictMode1.errors.txt @@ -1,4 +1,4 @@ -lib.d.ts(28,18): error TS2300: Duplicate identifier 'eval'. +lib.d.ts(32,18): error TS2300: Duplicate identifier 'eval'. tests/cases/compiler/variableDeclarationInStrictMode1.ts(2,5): error TS1100: Invalid use of 'eval' in strict mode. tests/cases/compiler/variableDeclarationInStrictMode1.ts(2,5): error TS2300: Duplicate identifier 'eval'. From a70624d4159e9de3f2c497d2f7ec52082fee7bf6 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 4 Oct 2016 12:08:58 -0700 Subject: [PATCH 002/176] Add test when require is just a function in external module --- ...quireAsFunctionInExternalModule.errors.txt | 20 ++++++++++ .../requireAsFunctionInExternalModule.js | 37 +++++++++++++++++++ .../requireAsFunctionInExternalModule.ts | 16 ++++++++ 3 files changed, 73 insertions(+) create mode 100644 tests/baselines/reference/requireAsFunctionInExternalModule.errors.txt create mode 100644 tests/baselines/reference/requireAsFunctionInExternalModule.js create mode 100644 tests/cases/compiler/requireAsFunctionInExternalModule.ts diff --git a/tests/baselines/reference/requireAsFunctionInExternalModule.errors.txt b/tests/baselines/reference/requireAsFunctionInExternalModule.errors.txt new file mode 100644 index 00000000000..58e3b145f35 --- /dev/null +++ b/tests/baselines/reference/requireAsFunctionInExternalModule.errors.txt @@ -0,0 +1,20 @@ +tests/cases/compiler/m2.ts(1,10): error TS2305: Module '"tests/cases/compiler/m"' has no exported member 'hello'. + + +==== tests/cases/compiler/c.js (0 errors) ==== + export default function require(a) { } + export function has(a) { return true } + +==== tests/cases/compiler/m.js (0 errors) ==== + import require, { has } from "./c" + export function hello() { } + if (has('ember-debug')) { + require('ember-debug'); + } + +==== tests/cases/compiler/m2.ts (1 errors) ==== + import { hello } from "./m"; + ~~~~~ +!!! error TS2305: Module '"tests/cases/compiler/m"' has no exported member 'hello'. + hello(); + \ No newline at end of file diff --git a/tests/baselines/reference/requireAsFunctionInExternalModule.js b/tests/baselines/reference/requireAsFunctionInExternalModule.js new file mode 100644 index 00000000000..a8b14db3138 --- /dev/null +++ b/tests/baselines/reference/requireAsFunctionInExternalModule.js @@ -0,0 +1,37 @@ +//// [tests/cases/compiler/requireAsFunctionInExternalModule.ts] //// + +//// [c.js] +export default function require(a) { } +export function has(a) { return true } + +//// [m.js] +import require, { has } from "./c" +export function hello() { } +if (has('ember-debug')) { + require('ember-debug'); +} + +//// [m2.ts] +import { hello } from "./m"; +hello(); + + +//// [c.js] +"use strict"; +function require(a) { } +exports.__esModule = true; +exports["default"] = require; +function has(a) { return true; } +exports.has = has; +//// [m.js] +"use strict"; +var c_1 = require("./c"); +function hello() { } +exports.hello = hello; +if (c_1.has('ember-debug')) { + c_1["default"]('ember-debug'); +} +//// [m2.js] +"use strict"; +var m_1 = require("./m"); +m_1.hello(); diff --git a/tests/cases/compiler/requireAsFunctionInExternalModule.ts b/tests/cases/compiler/requireAsFunctionInExternalModule.ts new file mode 100644 index 00000000000..1715090c329 --- /dev/null +++ b/tests/cases/compiler/requireAsFunctionInExternalModule.ts @@ -0,0 +1,16 @@ +// @allowjs: true +// @outDir: dist +// @Filename: c.js +export default function require(a) { } +export function has(a) { return true } + +// @Filename: m.js +import require, { has } from "./c" +export function hello() { } +if (has('ember-debug')) { + require('ember-debug'); +} + +// @Filename: m2.ts +import { hello } from "./m"; +hello(); From a830c844b7846f56a785d0ed8a2e75d4bec7a14f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 4 Oct 2016 12:25:07 -0700 Subject: [PATCH 003/176] Bind the source file as external module only if not already done Return type of require call is from external module resolution only if it doesnt resolve to local module Fixes #10931 --- src/compiler/binder.ts | 4 +- src/compiler/checker.ts | 5 ++- ...quireAsFunctionInExternalModule.errors.txt | 20 ---------- .../requireAsFunctionInExternalModule.symbols | 31 ++++++++++++++++ .../requireAsFunctionInExternalModule.types | 37 +++++++++++++++++++ 5 files changed, 75 insertions(+), 22 deletions(-) delete mode 100644 tests/baselines/reference/requireAsFunctionInExternalModule.errors.txt create mode 100644 tests/baselines/reference/requireAsFunctionInExternalModule.symbols create mode 100644 tests/baselines/reference/requireAsFunctionInExternalModule.types diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 2fe7d30ab56..c8785819b9e 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2000,7 +2000,9 @@ namespace ts { function setCommonJsModuleIndicator(node: Node) { if (!file.commonJsModuleIndicator) { file.commonJsModuleIndicator = node; - bindSourceFileAsExternalModule(); + if (!file.externalModuleIndicator) { + bindSourceFileAsExternalModule(); + } } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 422f2a70dcc..1354002a29e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12535,7 +12535,10 @@ namespace ts { } // In JavaScript files, calls to any identifier 'require' are treated as external module imports - if (isInJavaScriptFile(node) && isRequireCall(node, /*checkArgumentIsStringLiteral*/true)) { + if (isInJavaScriptFile(node) && + isRequireCall(node, /*checkArgumentIsStringLiteral*/true) && + // Make sure require is not a local function + !resolveName(node.expression, (node.expression).text, SymbolFlags.Value | SymbolFlags.ExportValue, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined)) { return resolveExternalModuleTypeByLiteral(node.arguments[0]); } diff --git a/tests/baselines/reference/requireAsFunctionInExternalModule.errors.txt b/tests/baselines/reference/requireAsFunctionInExternalModule.errors.txt deleted file mode 100644 index 58e3b145f35..00000000000 --- a/tests/baselines/reference/requireAsFunctionInExternalModule.errors.txt +++ /dev/null @@ -1,20 +0,0 @@ -tests/cases/compiler/m2.ts(1,10): error TS2305: Module '"tests/cases/compiler/m"' has no exported member 'hello'. - - -==== tests/cases/compiler/c.js (0 errors) ==== - export default function require(a) { } - export function has(a) { return true } - -==== tests/cases/compiler/m.js (0 errors) ==== - import require, { has } from "./c" - export function hello() { } - if (has('ember-debug')) { - require('ember-debug'); - } - -==== tests/cases/compiler/m2.ts (1 errors) ==== - import { hello } from "./m"; - ~~~~~ -!!! error TS2305: Module '"tests/cases/compiler/m"' has no exported member 'hello'. - hello(); - \ No newline at end of file diff --git a/tests/baselines/reference/requireAsFunctionInExternalModule.symbols b/tests/baselines/reference/requireAsFunctionInExternalModule.symbols new file mode 100644 index 00000000000..2f44d6b6611 --- /dev/null +++ b/tests/baselines/reference/requireAsFunctionInExternalModule.symbols @@ -0,0 +1,31 @@ +=== tests/cases/compiler/c.js === +export default function require(a) { } +>require : Symbol(require, Decl(c.js, 0, 0)) +>a : Symbol(a, Decl(c.js, 0, 32)) + +export function has(a) { return true } +>has : Symbol(has, Decl(c.js, 0, 38)) +>a : Symbol(a, Decl(c.js, 1, 20)) + +=== tests/cases/compiler/m.js === +import require, { has } from "./c" +>require : Symbol(require, Decl(m.js, 0, 6)) +>has : Symbol(has, Decl(m.js, 0, 17)) + +export function hello() { } +>hello : Symbol(hello, Decl(m.js, 0, 34)) + +if (has('ember-debug')) { +>has : Symbol(has, Decl(m.js, 0, 17)) + + require('ember-debug'); +>require : Symbol(require, Decl(m.js, 0, 6)) +} + +=== tests/cases/compiler/m2.ts === +import { hello } from "./m"; +>hello : Symbol(hello, Decl(m2.ts, 0, 8)) + +hello(); +>hello : Symbol(hello, Decl(m2.ts, 0, 8)) + diff --git a/tests/baselines/reference/requireAsFunctionInExternalModule.types b/tests/baselines/reference/requireAsFunctionInExternalModule.types new file mode 100644 index 00000000000..2ce5f3aed38 --- /dev/null +++ b/tests/baselines/reference/requireAsFunctionInExternalModule.types @@ -0,0 +1,37 @@ +=== tests/cases/compiler/c.js === +export default function require(a) { } +>require : (a: any) => void +>a : any + +export function has(a) { return true } +>has : (a: any) => boolean +>a : any +>true : true + +=== tests/cases/compiler/m.js === +import require, { has } from "./c" +>require : (a: any) => void +>has : (a: any) => boolean + +export function hello() { } +>hello : () => void + +if (has('ember-debug')) { +>has('ember-debug') : boolean +>has : (a: any) => boolean +>'ember-debug' : "ember-debug" + + require('ember-debug'); +>require('ember-debug') : void +>require : (a: any) => void +>'ember-debug' : "ember-debug" +} + +=== tests/cases/compiler/m2.ts === +import { hello } from "./m"; +>hello : () => void + +hello(); +>hello() : void +>hello : () => void + From 8421f735322e88f67b4b17feb2720e54c61639ea Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 5 Oct 2016 17:20:42 -0700 Subject: [PATCH 004/176] Report missing global diagnostics --- src/compiler/checker.ts | 172 +++++++++++++++++++++++++++++++++------- src/compiler/core.ts | 33 +++++++- 2 files changed, 173 insertions(+), 32 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ef428f26de9..9840df7d805 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5787,6 +5787,90 @@ namespace ts { } } + function getStaticTypeFromTypeNode(node: TypeNode) { + switch (node.kind) { + case SyntaxKind.AnyKeyword: + case SyntaxKind.JSDocAllType: + case SyntaxKind.JSDocUnknownType: + return anyType; + case SyntaxKind.StringKeyword: + return stringType; + case SyntaxKind.NumberKeyword: + return numberType; + case SyntaxKind.BooleanKeyword: + return booleanType; + case SyntaxKind.SymbolKeyword: + return esSymbolType; + case SyntaxKind.VoidKeyword: + return voidType; + case SyntaxKind.UndefinedKeyword: + return undefinedType; + case SyntaxKind.NullKeyword: + return nullType; + case SyntaxKind.NeverKeyword: + return neverType; + case SyntaxKind.JSDocNullKeyword: + return nullType; + case SyntaxKind.JSDocUndefinedKeyword: + return undefinedType; + case SyntaxKind.JSDocNeverKeyword: + return neverType; + case SyntaxKind.ThisType: + case SyntaxKind.ThisKeyword: + return getTypeFromThisTypeNode(node); + case SyntaxKind.LiteralType: + return getTypeFromLiteralTypeNode(node); + case SyntaxKind.JSDocLiteralType: + return getTypeFromLiteralTypeNode((node).literal); + case SyntaxKind.TypeReference: + case SyntaxKind.JSDocTypeReference: + return getTypeFromTypeReference(node); + case SyntaxKind.TypePredicate: + return booleanType; + case SyntaxKind.ExpressionWithTypeArguments: + return getTypeFromTypeReference(node); + case SyntaxKind.TypeQuery: + return getTypeFromTypeQueryNode(node); + case SyntaxKind.ArrayType: + case SyntaxKind.JSDocArrayType: + return getTypeFromArrayTypeNode(node); + case SyntaxKind.TupleType: + return getTypeFromTupleTypeNode(node); + case SyntaxKind.UnionType: + case SyntaxKind.JSDocUnionType: + return getTypeFromUnionTypeNode(node, aliasSymbol, aliasTypeArguments); + case SyntaxKind.IntersectionType: + return getTypeFromIntersectionTypeNode(node, aliasSymbol, aliasTypeArguments); + case SyntaxKind.ParenthesizedType: + case SyntaxKind.JSDocNullableType: + case SyntaxKind.JSDocNonNullableType: + case SyntaxKind.JSDocConstructorType: + case SyntaxKind.JSDocThisType: + case SyntaxKind.JSDocOptionalType: + return getTypeFromTypeNode((node).type); + case SyntaxKind.JSDocRecordType: + return getTypeFromTypeNode((node as JSDocRecordType).literal); + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.TypeLiteral: + case SyntaxKind.JSDocTypeLiteral: + case SyntaxKind.JSDocFunctionType: + return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node, aliasSymbol, aliasTypeArguments); + // This function assumes that an identifier or qualified name is a type expression + // Callers should first ensure this by calling isTypeNode + case SyntaxKind.Identifier: + case SyntaxKind.QualifiedName: + const symbol = getSymbolAtLocation(node); + return symbol && getDeclaredTypeOfSymbol(symbol); + case SyntaxKind.JSDocTupleType: + return getTypeFromJSDocTupleType(node); + case SyntaxKind.JSDocVariadicType: + return getTypeFromJSDocVariadicType(node); + default: + return unknownType; + } + } + function instantiateList(items: T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): T[] { if (items && items.length) { const result: T[] = []; @@ -15181,31 +15265,24 @@ namespace ts { } /** - * Checks the return type of an async function to ensure it is a compatible - * Promise implementation. - * @param node The signature to check - * @param returnType The return type for the function - * @remarks - * This checks that an async function has a valid Promise-compatible return type, - * and returns the *awaited type* of the promise. An async function has a valid - * Promise-compatible return type if the resolved value of the return type has a - * construct signature that takes in an `initializer` function that in turn supplies - * a `resolve` function as one of its arguments and results in an object with a - * callable `then` signature. - */ + * Checks the return type of an async function to ensure it is a compatible + * Promise implementation. + * + * This checks that an async function has a valid Promise-compatible return type, + * and returns the *awaited type* of the promise. An async function has a valid + * Promise-compatible return type if the resolved value of the return type has a + * construct signature that takes in an `initializer` function that in turn supplies + * a `resolve` function as one of its arguments and results in an object with a + * callable `then` signature. + * + * @param node The signature to check + */ function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration): Type { if (languageVersion >= ScriptTarget.ES6) { const returnType = getTypeFromTypeNode(node.type); return checkCorrectPromiseType(returnType, node.type, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type); } - const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType(); - if (globalPromiseConstructorLikeType === emptyObjectType) { - // If we couldn't resolve the global PromiseConstructorLike type we cannot verify - // compatibility with __awaiter. - return unknownType; - } - // As part of our emit for an async function, we will need to emit the entity name of // the return type annotation as an expression. To meet the necessary runtime semantics // for __awaiter, we must also check that the type of the declaration (e.g. the static @@ -15230,18 +15307,35 @@ namespace ts { // then(...): Promise; // } // - // When we get the type of the `Promise` symbol here, we get the type of the static - // side of the `Promise` class, which would be `{ new (...): Promise }`. + + const promiseName = getEntityNameFromTypeNode(node.type); + const rootName = getFirstIdentifier(promiseName); + + // Mark the root symbol as referenced. + getSymbolLinks(rootName.symbol).referenced = true; const promiseType = getTypeFromTypeNode(node.type); if (promiseType === unknownType && compilerOptions.isolatedModules) { // If we are compiling with isolatedModules, we may not be able to resolve the - // type as a value. As such, we will just return unknownType; + // type as a value. As such, we will just return unknownType. return unknownType; } + const promiseConstructorType = getStaticTypeFromTypeNode(node.type); + + const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType(); + if (globalPromiseConstructorLikeType === emptyObjectType) { + // If we couldn't resolve the global PromiseConstructorLike type we cannot verify + // compatibility with __awaiter. + error(node.type || node.name || node, Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type); + return unknownType; + } + + // When we get the type of the `Promise` symbol here, we get the type of the static + // side of the `Promise` class, which would be `{ new (...): Promise }`. + const promiseConstructor = getNodeLinks(node.type).resolvedSymbol; - if (!promiseConstructor || !symbolIsValue(promiseConstructor)) { + if (!promiseConstructor) { // try to fall back to global promise type. const typeName = promiseConstructor ? symbolToString(promiseConstructor) @@ -15259,12 +15353,10 @@ namespace ts { } // Verify there is no local declaration that could collide with the promise constructor. - const promiseName = getEntityNameFromTypeNode(node.type); - const promiseNameOrNamespaceRoot = getFirstIdentifier(promiseName); - const rootSymbol = getSymbol(node.locals, promiseNameOrNamespaceRoot.text, SymbolFlags.Value); + const rootSymbol = getSymbol(node.locals, rootName.text, SymbolFlags.Value); if (rootSymbol) { error(rootSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions, - promiseNameOrNamespaceRoot.text, + rootName.text, getFullyQualifiedName(promiseConstructor)); return unknownType; } @@ -18086,9 +18178,33 @@ namespace ts { function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] { throwIfNonDiagnosticsProducing(); if (sourceFile) { + // Some global diagnostics are deferred until they are needed and + // may not be reported in the firt call to getGlobalDiagnostics. + // We should catch these changes and report them. + const previousGlobalDiagnostics = diagnostics.getGlobalDiagnostics(); + const previousGlobalDiagnosticsSize = previousGlobalDiagnostics.length; + checkSourceFile(sourceFile); - return diagnostics.getDiagnostics(sourceFile.fileName); + + const semanticDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName); + const currentGlobalDiagnostics = diagnostics.getGlobalDiagnostics(); + if (currentGlobalDiagnostics !== previousGlobalDiagnostics) { + // If the arrays are not the same reference, new diagnostics were added. + const deferredGlobalDiagnostics = relativeComplement(previousGlobalDiagnostics, currentGlobalDiagnostics, compareDiagnostics); + return concatenate(deferredGlobalDiagnostics, semanticDiagnostics); + } + else if (previousGlobalDiagnosticsSize === 0 && currentGlobalDiagnostics.length > 0) { + // If the arrays are the same reference, but the length has changed, a single + // new diagnostic was added as DiagnosticCollection attempts to reuse the + // same array. + return concatenate(currentGlobalDiagnostics, semanticDiagnostics); + } + + return semanticDiagnostics; } + + // Global diagnostics are always added when a file is not provided to + // getDiagnostics forEach(host.getSourceFiles(), checkSourceFile); return diagnostics.getDiagnostics(); } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index e63bcbf8474..439e5609566 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -400,8 +400,8 @@ namespace ts { } export function concatenate(array1: T[], array2: T[]): T[] { - if (!array2 || !array2.length) return array1; - if (!array1 || !array1.length) return array2; + if (isEmptyArray(array2)) return array1; + if (isEmptyArray(array1)) return array2; return [...array1, ...array2]; } @@ -443,6 +443,27 @@ namespace ts { return result || array; } + /** + * Gets the relative complement of `arrayA` with respect to `b`, returning the elements that + * are not present in `arrayA` but are present in `arrayB`. Assumes both arrays are sorted + * based on the provided comparer. + */ + export function relativeComplement(arrayA: T[] | undefined, arrayB: T[] | undefined, comparer: (x: T, y: T) => Comparison = compareValues, offsetA: number = 0, offsetB: number = 0): T[] | undefined { + if (!arrayB || !arrayA || arrayB.length === 0 || arrayA.length === 0) return arrayB; + const result: T[] = []; + outer: for (; offsetB < arrayB.length; offsetB++) { + inner: for (; offsetA < arrayA.length; offsetA++) { + switch (comparer(arrayB[offsetB], arrayA[offsetA])) { + case Comparison.LessThan: break inner; + case Comparison.EqualTo: continue outer; + case Comparison.GreaterThan: continue inner; + } + } + result.push(arrayB[offsetB]); + } + return result; + } + export function sum(array: any[], prop: string): number { let result = 0; for (const v of array) { @@ -505,12 +526,12 @@ namespace ts { * @param array A sorted array whose first element must be no larger than number * @param number The value to be searched for in the array. */ - export function binarySearch(array: T[], value: T, comparer?: (v1: T, v2: T) => number): number { + export function binarySearch(array: T[], value: T, comparer?: (v1: T, v2: T) => number, offset?: number): number { if (!array || array.length === 0) { return -1; } - let low = 0; + let low = offset || 0; let high = array.length - 1; comparer = comparer !== undefined ? comparer @@ -829,6 +850,10 @@ namespace ts { return Array.isArray ? Array.isArray(value) : value instanceof Array; } + export function isEmptyArray(value: any[] | undefined): boolean { + return !value || !value.length; + } + export function memoize(callback: () => T): () => T { let value: T; return () => { From 763e78dc066bc26e2e4caa08d600b702e1f2fd4f Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 6 Oct 2016 10:33:44 -0700 Subject: [PATCH 005/176] Simplify async return type checking --- src/compiler/checker.ts | 209 +++++------------- src/compiler/diagnosticMessages.json | 2 +- src/compiler/transformers/ts.ts | 15 +- src/compiler/utilities.ts | 35 ++- .../asyncAliasReturnType_es5.errors.txt | 10 + .../asyncAwaitIsolatedModules_es5.js | 1 + .../asyncFunctionDeclaration15_es5.errors.txt | 24 +- 7 files changed, 108 insertions(+), 188 deletions(-) create mode 100644 tests/baselines/reference/asyncAliasReturnType_es5.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9840df7d805..da5bd3a6590 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5787,90 +5787,6 @@ namespace ts { } } - function getStaticTypeFromTypeNode(node: TypeNode) { - switch (node.kind) { - case SyntaxKind.AnyKeyword: - case SyntaxKind.JSDocAllType: - case SyntaxKind.JSDocUnknownType: - return anyType; - case SyntaxKind.StringKeyword: - return stringType; - case SyntaxKind.NumberKeyword: - return numberType; - case SyntaxKind.BooleanKeyword: - return booleanType; - case SyntaxKind.SymbolKeyword: - return esSymbolType; - case SyntaxKind.VoidKeyword: - return voidType; - case SyntaxKind.UndefinedKeyword: - return undefinedType; - case SyntaxKind.NullKeyword: - return nullType; - case SyntaxKind.NeverKeyword: - return neverType; - case SyntaxKind.JSDocNullKeyword: - return nullType; - case SyntaxKind.JSDocUndefinedKeyword: - return undefinedType; - case SyntaxKind.JSDocNeverKeyword: - return neverType; - case SyntaxKind.ThisType: - case SyntaxKind.ThisKeyword: - return getTypeFromThisTypeNode(node); - case SyntaxKind.LiteralType: - return getTypeFromLiteralTypeNode(node); - case SyntaxKind.JSDocLiteralType: - return getTypeFromLiteralTypeNode((node).literal); - case SyntaxKind.TypeReference: - case SyntaxKind.JSDocTypeReference: - return getTypeFromTypeReference(node); - case SyntaxKind.TypePredicate: - return booleanType; - case SyntaxKind.ExpressionWithTypeArguments: - return getTypeFromTypeReference(node); - case SyntaxKind.TypeQuery: - return getTypeFromTypeQueryNode(node); - case SyntaxKind.ArrayType: - case SyntaxKind.JSDocArrayType: - return getTypeFromArrayTypeNode(node); - case SyntaxKind.TupleType: - return getTypeFromTupleTypeNode(node); - case SyntaxKind.UnionType: - case SyntaxKind.JSDocUnionType: - return getTypeFromUnionTypeNode(node, aliasSymbol, aliasTypeArguments); - case SyntaxKind.IntersectionType: - return getTypeFromIntersectionTypeNode(node, aliasSymbol, aliasTypeArguments); - case SyntaxKind.ParenthesizedType: - case SyntaxKind.JSDocNullableType: - case SyntaxKind.JSDocNonNullableType: - case SyntaxKind.JSDocConstructorType: - case SyntaxKind.JSDocThisType: - case SyntaxKind.JSDocOptionalType: - return getTypeFromTypeNode((node).type); - case SyntaxKind.JSDocRecordType: - return getTypeFromTypeNode((node as JSDocRecordType).literal); - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.TypeLiteral: - case SyntaxKind.JSDocTypeLiteral: - case SyntaxKind.JSDocFunctionType: - return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node, aliasSymbol, aliasTypeArguments); - // This function assumes that an identifier or qualified name is a type expression - // Callers should first ensure this by calling isTypeNode - case SyntaxKind.Identifier: - case SyntaxKind.QualifiedName: - const symbol = getSymbolAtLocation(node); - return symbol && getDeclaredTypeOfSymbol(symbol); - case SyntaxKind.JSDocTupleType: - return getTypeFromJSDocTupleType(node); - case SyntaxKind.JSDocVariadicType: - return getTypeFromJSDocVariadicType(node); - default: - return unknownType; - } - } - function instantiateList(items: T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): T[] { if (items && items.length) { const result: T[] = []; @@ -15308,56 +15224,55 @@ namespace ts { // } // - const promiseName = getEntityNameFromTypeNode(node.type); - const rootName = getFirstIdentifier(promiseName); - - // Mark the root symbol as referenced. - getSymbolLinks(rootName.symbol).referenced = true; + // Always mark the type node as referenced if it points to a value + markTypeNodeAsReferenced(node.type); + const promiseConstructorName = getEntityNameFromTypeNode(node.type); const promiseType = getTypeFromTypeNode(node.type); - if (promiseType === unknownType && compilerOptions.isolatedModules) { - // If we are compiling with isolatedModules, we may not be able to resolve the - // type as a value. As such, we will just return unknownType. + if (promiseType === unknownType) { + if (!compilerOptions.isolatedModules) { + if (promiseConstructorName) { + error(node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); + } + else { + error(node.type, Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type); + } + } return unknownType; } - const promiseConstructorType = getStaticTypeFromTypeNode(node.type); + if (promiseConstructorName === undefined) { + error(node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, typeToString(promiseType)); + return unknownType; + } + + const promiseConstructorSymbol = resolveEntityName(promiseConstructorName, SymbolFlags.Value, /*ignoreErrors*/ true); + const promiseConstructorType = promiseConstructorSymbol ? getTypeOfSymbol(promiseConstructorSymbol) : unknownType; + if (promiseConstructorType === unknownType) { + error(node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); + return unknownType; + } const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType(); if (globalPromiseConstructorLikeType === emptyObjectType) { // If we couldn't resolve the global PromiseConstructorLike type we cannot verify // compatibility with __awaiter. - error(node.type || node.name || node, Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type); + error(node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); return unknownType; } - // When we get the type of the `Promise` symbol here, we get the type of the static - // side of the `Promise` class, which would be `{ new (...): Promise }`. - - const promiseConstructor = getNodeLinks(node.type).resolvedSymbol; - if (!promiseConstructor) { - // try to fall back to global promise type. - const typeName = promiseConstructor - ? symbolToString(promiseConstructor) - : typeToString(promiseType); - return checkCorrectPromiseType(promiseType, node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type, typeName); - } - - // If the Promise constructor, resolved locally, is an alias symbol we should mark it as referenced. - checkReturnTypeAnnotationAsExpression(node); - - // Validate the promise constructor type. - const promiseConstructorType = getTypeOfSymbol(promiseConstructor); - if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, node.type, Diagnostics.Type_0_is_not_a_valid_async_function_return_type)) { + if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, node.type, + Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value)) { return unknownType; } // Verify there is no local declaration that could collide with the promise constructor. - const rootSymbol = getSymbol(node.locals, rootName.text, SymbolFlags.Value); - if (rootSymbol) { - error(rootSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions, + const rootName = promiseConstructorName && getFirstIdentifier(promiseConstructorName); + const collidingSymbol = getSymbol(node.locals, rootName.text, SymbolFlags.Value); + if (collidingSymbol) { + error(collidingSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions, rootName.text, - getFullyQualifiedName(promiseConstructor)); + entityNameToString(promiseConstructorName)); return unknownType; } @@ -15415,44 +15330,19 @@ namespace ts { errorInfo); } - /** Checks a type reference node as an expression. */ - function checkTypeNodeAsExpression(node: TypeNode) { - // When we are emitting type metadata for decorators, we need to try to check the type - // as if it were an expression so that we can emit the type in a value position when we - // serialize the type metadata. - if (node && node.kind === SyntaxKind.TypeReference) { - const root = getFirstIdentifier((node).typeName); - const meaning = root.parent.kind === SyntaxKind.TypeReference ? SymbolFlags.Type : SymbolFlags.Namespace; - // Resolve type so we know which symbol is referenced - const rootSymbol = resolveName(root, root.text, meaning | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined); - // Resolved symbol is alias - if (rootSymbol && rootSymbol.flags & SymbolFlags.Alias) { - const aliasTarget = resolveAlias(rootSymbol); - // If alias has value symbol - mark alias as referenced - if (aliasTarget.flags & SymbolFlags.Value && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol))) { - markAliasSymbolAsReferenced(rootSymbol); - } - } - } - } - /** - * Checks the type annotation of an accessor declaration or property declaration as - * an expression if it is a type reference to a type with a value declaration. - */ - function checkTypeAnnotationAsExpression(node: VariableLikeDeclaration) { - checkTypeNodeAsExpression((node).type); - } - - function checkReturnTypeAnnotationAsExpression(node: FunctionLikeDeclaration) { - checkTypeNodeAsExpression(node.type); - } - - /** Checks the type annotation of the parameters of a function/method or the constructor of a class as expressions */ - function checkParameterTypeAnnotationsAsExpressions(node: FunctionLikeDeclaration) { - // ensure all type annotations with a value declaration are checked as an expression - for (const parameter of node.parameters) { - checkTypeAnnotationAsExpression(parameter); + * If a TypeNode can be resolved to a value symbol imported from an external module, it is + * marked as referenced to prevent import elision. + */ + function markTypeNodeAsReferenced(node: TypeNode) { + const typeName = node && getEntityNameFromTypeNode(node); + const rootName = typeName && getFirstIdentifier(typeName); + const rootSymbol = rootName && resolveName(rootName, rootName.text, (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined); + if (rootSymbol + && rootSymbol.flags & SymbolFlags.Alias + && symbolIsValue(rootSymbol) + && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol))) { + markAliasSymbolAsReferenced(rootSymbol); } } @@ -15478,20 +15368,25 @@ namespace ts { case SyntaxKind.ClassDeclaration: const constructor = getFirstConstructorWithBody(node); if (constructor) { - checkParameterTypeAnnotationsAsExpressions(constructor); + for (const parameter of constructor.parameters) { + markTypeNodeAsReferenced(parameter.type); + } } break; case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: - checkParameterTypeAnnotationsAsExpressions(node); - checkReturnTypeAnnotationAsExpression(node); + for (const parameter of (node).parameters) { + markTypeNodeAsReferenced(parameter.type); + } + + markTypeNodeAsReferenced((node).type); break; case SyntaxKind.PropertyDeclaration: case SyntaxKind.Parameter: - checkTypeAnnotationAsExpression(node); + markTypeNodeAsReferenced((node).type); break; } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 71d7a978d1b..815ca9a9d67 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -163,7 +163,7 @@ "category": "Error", "code": 1054 }, - "Type '{0}' is not a valid async function return type.": { + "Type '{0}' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.": { "category": "Error", "code": 1055 }, diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 2419ffa4c3b..352d6f6f1dc 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -2329,15 +2329,16 @@ namespace ts { } function getPromiseConstructor(type: TypeNode) { - const typeName = getEntityNameFromTypeNode(type); - if (typeName && isEntityName(typeName)) { - const serializationKind = resolver.getTypeReferenceSerializationKind(typeName); - if (serializationKind === TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue - || serializationKind === TypeReferenceSerializationKind.Unknown) { - return typeName; + if (type) { + const typeName = getEntityNameFromTypeNode(type); + if (typeName && isEntityName(typeName)) { + const serializationKind = resolver.getTypeReferenceSerializationKind(typeName); + if (serializationKind === TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue + || serializationKind === TypeReferenceSerializationKind.Unknown) { + return typeName; + } } } - return undefined; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 62813e9447c..b6d81e3f242 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -503,6 +503,17 @@ namespace ts { return getFullWidth(name) === 0 ? "(Missing)" : getTextOfNode(name); } + export function entityNameToString(name: EntityNameOrEntityNameExpression): string { + switch (name.kind) { + case SyntaxKind.Identifier: + return getFullWidth(name) === 0 ? unescapeIdentifier((name).text) : getTextOfNode(name); + case SyntaxKind.QualifiedName: + return entityNameToString((name).left) + "." + entityNameToString((name).right); + case SyntaxKind.PropertyAccessExpression: + return entityNameToString((name).expression) + "." + entityNameToString((name).name); + } + } + export function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): Diagnostic { const sourceFile = getSourceFileOfNode(node); const span = getErrorSpanForNode(sourceFile, node); @@ -1054,17 +1065,19 @@ namespace ts { } export function getEntityNameFromTypeNode(node: TypeNode): EntityNameOrEntityNameExpression { - if (node) { - switch (node.kind) { - case SyntaxKind.TypeReference: - return (node).typeName; - case SyntaxKind.ExpressionWithTypeArguments: - Debug.assert(isEntityNameExpression((node).expression)); - return (node).expression; - case SyntaxKind.Identifier: - case SyntaxKind.QualifiedName: - return (node); - } + switch (node.kind) { + case SyntaxKind.TypeReference: + case SyntaxKind.JSDocTypeReference: + return (node).typeName; + + case SyntaxKind.ExpressionWithTypeArguments: + return isEntityNameExpression((node).expression) + ? (node).expression + : undefined; + + case SyntaxKind.Identifier: + case SyntaxKind.QualifiedName: + return (node); } return undefined; diff --git a/tests/baselines/reference/asyncAliasReturnType_es5.errors.txt b/tests/baselines/reference/asyncAliasReturnType_es5.errors.txt new file mode 100644 index 00000000000..5e524e265e2 --- /dev/null +++ b/tests/baselines/reference/asyncAliasReturnType_es5.errors.txt @@ -0,0 +1,10 @@ +tests/cases/conformance/async/es5/asyncAliasReturnType_es5.ts(3,21): error TS1055: Type 'PromiseAlias' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value. + + +==== tests/cases/conformance/async/es5/asyncAliasReturnType_es5.ts (1 errors) ==== + type PromiseAlias = Promise; + + async function f(): PromiseAlias { + ~~~~~~~~~~~~~~~~~~ +!!! error TS1055: Type 'PromiseAlias' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value. + } \ No newline at end of file diff --git a/tests/baselines/reference/asyncAwaitIsolatedModules_es5.js b/tests/baselines/reference/asyncAwaitIsolatedModules_es5.js index 7eb2157c026..d1d1db3e329 100644 --- a/tests/baselines/reference/asyncAwaitIsolatedModules_es5.js +++ b/tests/baselines/reference/asyncAwaitIsolatedModules_es5.js @@ -77,6 +77,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) { } }; var _this = this; +var missing_1 = require("missing"); function f0() { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/]; diff --git a/tests/baselines/reference/asyncFunctionDeclaration15_es5.errors.txt b/tests/baselines/reference/asyncFunctionDeclaration15_es5.errors.txt index 3ea03927aa9..5fa33233912 100644 --- a/tests/baselines/reference/asyncFunctionDeclaration15_es5.errors.txt +++ b/tests/baselines/reference/asyncFunctionDeclaration15_es5.errors.txt @@ -1,9 +1,9 @@ -tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(6,23): error TS1055: Type '{}' is not a valid async function return type. -tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(7,23): error TS1055: Type 'any' is not a valid async function return type. -tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(8,23): error TS1055: Type 'number' is not a valid async function return type. -tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(9,23): error TS1055: Type 'PromiseLike' is not a valid async function return type. -tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(10,23): error TS1055: Type 'typeof Thenable' is not a valid async function return type. -tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(10,23): error TS1055: Type 'typeof Thenable' is not a valid async function return type. +tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(6,23): error TS1055: Type '{}' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value. +tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(7,23): error TS1055: Type 'any' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value. +tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(8,23): error TS1055: Type 'number' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value. +tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(9,23): error TS1055: Type 'PromiseLike' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value. +tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(10,23): error TS1055: Type 'typeof Thenable' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value. +tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(10,23): error TS1055: Type 'typeof Thenable' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value. Type 'Thenable' is not assignable to type 'PromiseLike'. Types of property 'then' are incompatible. Type '() => void' is not assignable to type '{ (onfulfilled?: (value: any) => any, onrejected?: (reason: any) => any): PromiseLike; (onfulfilled: (value: any) => any, onrejected: (reason: any) => TResult | PromiseLike): PromiseLike; (onfulfilled: (value: any) => TResult | PromiseLike, onrejected?: (reason: any) => TResult | PromiseLike): PromiseLike; (onfulfilled: (value: any) => TResult1 | PromiseLike, onrejected: (reason: any) => TResult2 | PromiseLike): PromiseLike; }'. @@ -20,21 +20,21 @@ tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration1 async function fn1() { } // valid: Promise async function fn2(): { } { } // error ~~~ -!!! error TS1055: Type '{}' is not a valid async function return type. +!!! error TS1055: Type '{}' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value. async function fn3(): any { } // error ~~~ -!!! error TS1055: Type 'any' is not a valid async function return type. +!!! error TS1055: Type 'any' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value. async function fn4(): number { } // error ~~~~~~ -!!! error TS1055: Type 'number' is not a valid async function return type. +!!! error TS1055: Type 'number' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value. async function fn5(): PromiseLike { } // error ~~~~~~~~~~~~~~~~~ -!!! error TS1055: Type 'PromiseLike' is not a valid async function return type. +!!! error TS1055: Type 'PromiseLike' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value. async function fn6(): Thenable { } // error ~~~~~~~~ -!!! error TS1055: Type 'typeof Thenable' is not a valid async function return type. +!!! error TS1055: Type 'typeof Thenable' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value. ~~~~~~~~ -!!! error TS1055: Type 'typeof Thenable' is not a valid async function return type. +!!! error TS1055: Type 'typeof Thenable' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value. !!! error TS1055: Type 'Thenable' is not assignable to type 'PromiseLike'. !!! error TS1055: Types of property 'then' are incompatible. !!! error TS1055: Type '() => void' is not assignable to type '{ (onfulfilled?: (value: any) => any, onrejected?: (reason: any) => any): PromiseLike; (onfulfilled: (value: any) => any, onrejected: (reason: any) => TResult | PromiseLike): PromiseLike; (onfulfilled: (value: any) => TResult | PromiseLike, onrejected?: (reason: any) => TResult | PromiseLike): PromiseLike; (onfulfilled: (value: any) => TResult1 | PromiseLike, onrejected: (reason: any) => TResult2 | PromiseLike): PromiseLike; }'. From aadcbcc083d984f266cfb6188c5dc4dd2cd908fa Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 26 Sep 2016 11:33:25 -0700 Subject: [PATCH 006/176] Use native maps when they're available --- Jakefile.js | 2 + scripts/processDiagnosticMessages.ts | 18 +- src/compiler/binder.ts | 44 +- src/compiler/checker.ts | 480 +++++++------- src/compiler/commandLineParser.ts | 130 ++-- src/compiler/core.ts | 287 +------- src/compiler/dataStructures.ts | 624 ++++++++++++++++++ src/compiler/declarationEmitter.ts | 18 +- src/compiler/emitter.ts | 18 +- src/compiler/factory.ts | 18 +- src/compiler/parser.ts | 6 +- src/compiler/performance.ts | 30 +- src/compiler/program.ts | 72 +- src/compiler/scanner.ts | 21 +- src/compiler/sourcemap.ts | 2 +- src/compiler/sys.ts | 21 +- src/compiler/transformer.ts | 20 +- src/compiler/transformers/es6.ts | 31 +- src/compiler/transformers/generators.ts | 32 +- src/compiler/transformers/jsx.ts | 8 +- src/compiler/transformers/module/module.ts | 48 +- src/compiler/transformers/module/system.ts | 12 +- src/compiler/transformers/ts.ts | 20 +- src/compiler/tsc.ts | 34 +- src/compiler/tsconfig.json | 1 + src/compiler/types.ts | 69 +- src/compiler/utilities.ts | 56 +- src/compiler/visitor.ts | 54 +- src/harness/fourslash.ts | 46 +- src/harness/harness.ts | 27 +- src/harness/harnessLanguageService.ts | 4 +- src/harness/loggedIO.ts | 10 +- src/harness/projectsRunner.ts | 21 +- .../unittests/cachingInServerLSHost.ts | 23 +- src/harness/unittests/moduleResolution.ts | 64 +- .../unittests/reuseProgramStructure.ts | 27 +- src/harness/unittests/session.ts | 22 +- .../unittests/tsserverProjectSystem.ts | 39 +- src/server/builder.ts | 15 +- src/server/client.ts | 8 +- src/server/editorServices.ts | 57 +- src/server/lsHost.ts | 20 +- src/server/project.ts | 51 +- src/server/scriptInfo.ts | 4 +- src/server/session.ts | 10 +- src/server/tsconfig.library.json | 2 +- src/server/typingsCache.ts | 27 +- .../typingsInstaller/typingsInstaller.ts | 42 +- src/server/utilities.ts | 43 +- src/services/classifier.ts | 6 +- src/services/completions.ts | 55 +- src/services/documentHighlights.ts | 6 +- src/services/documentRegistry.ts | 39 +- src/services/findAllReferences.ts | 31 +- src/services/formatting/rules.ts | 2 +- src/services/goToDefinition.ts | 2 +- src/services/jsTyping.ts | 45 +- src/services/navigateTo.ts | 19 +- src/services/navigationBar.ts | 8 +- src/services/patternMatcher.ts | 8 +- src/services/services.ts | 28 +- src/services/shims.ts | 2 +- src/services/signatureHelp.ts | 2 +- src/services/transpile.ts | 6 +- src/services/types.ts | 4 +- tslint.json | 3 +- 66 files changed, 1678 insertions(+), 1326 deletions(-) create mode 100644 src/compiler/dataStructures.ts diff --git a/Jakefile.js b/Jakefile.js index 64a8553ef39..2e4a30c90c6 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -57,6 +57,7 @@ function measure(marker) { } var compilerSources = [ + "dataStructures.ts", "core.ts", "performance.ts", "sys.ts", @@ -91,6 +92,7 @@ var compilerSources = [ }); var servicesSources = [ + "dataStructures.ts", "core.ts", "performance.ts", "sys.ts", diff --git a/scripts/processDiagnosticMessages.ts b/scripts/processDiagnosticMessages.ts index e5eaa46c8e5..d6133e6dab0 100644 --- a/scripts/processDiagnosticMessages.ts +++ b/scripts/processDiagnosticMessages.ts @@ -27,7 +27,7 @@ function main(): void { var inputFilePath = sys.args[0].replace(/\\/g, "/"); var inputStr = sys.readFile(inputFilePath); - + var diagnosticMessages: InputDiagnosticMessageTable = JSON.parse(inputStr); var names = Utilities.getObjectKeys(diagnosticMessages); @@ -44,7 +44,7 @@ function main(): void { function checkForUniqueCodes(messages: string[], diagnosticTable: InputDiagnosticMessageTable) { const originalMessageForCode: string[] = []; let numConflicts = 0; - + for (const currentMessage of messages) { const code = diagnosticTable[currentMessage].code; @@ -68,19 +68,19 @@ function checkForUniqueCodes(messages: string[], diagnosticTable: InputDiagnosti } } -function buildUniqueNameMap(names: string[]): ts.Map { - var nameMap = ts.createMap(); +function buildUniqueNameMap(names: string[]): ts.Map { + var nameMap = new ts.StringMap(); var uniqueNames = NameGenerator.ensureUniqueness(names, /* isCaseSensitive */ false, /* isFixed */ undefined); for (var i = 0; i < names.length; i++) { - nameMap[names[i]] = uniqueNames[i]; + nameMap.set(names[i], uniqueNames[i]); } return nameMap; } -function buildInfoFileOutput(messageTable: InputDiagnosticMessageTable, nameMap: ts.Map): string { +function buildInfoFileOutput(messageTable: InputDiagnosticMessageTable, nameMap: ts.Map): string { var result = '// \r\n' + '/// \r\n' + @@ -91,7 +91,7 @@ function buildInfoFileOutput(messageTable: InputDiagnosticMessageTable, nameMap: for (var i = 0; i < names.length; i++) { var name = names[i]; var diagnosticDetails = messageTable[name]; - var propName = convertPropertyName(nameMap[name]); + var propName = convertPropertyName(nameMap.get(name)); result += ' ' + propName + @@ -107,14 +107,14 @@ function buildInfoFileOutput(messageTable: InputDiagnosticMessageTable, nameMap: return result; } -function buildDiagnosticMessageOutput(messageTable: InputDiagnosticMessageTable, nameMap: ts.Map): string { +function buildDiagnosticMessageOutput(messageTable: InputDiagnosticMessageTable, nameMap: ts.Map): string { var result = '{'; var names = Utilities.getObjectKeys(messageTable); for (var i = 0; i < names.length; i++) { var name = names[i]; var diagnosticDetails = messageTable[name]; - var propName = convertPropertyName(nameMap[name]); + var propName = convertPropertyName(nameMap.get(name)); result += '\r\n "' + createKey(propName, diagnosticDetails.code) + '"' + ' : "' + name.replace(/[\"]/g, '\\"') + '"'; if (i !== names.length - 1) { diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 04b115b4dd9..deee2061005 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -126,7 +126,7 @@ namespace ts { let symbolCount = 0; let Symbol: { new (flags: SymbolFlags, name: string): Symbol }; - let classifiableNames: Map; + let classifiableNames: Set; const unreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; const reportedUnreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; @@ -140,7 +140,7 @@ namespace ts { options = opts; languageVersion = getEmitScriptTarget(options); inStrictMode = !!file.externalModuleIndicator; - classifiableNames = createMap(); + classifiableNames = new StringSet(); symbolCount = 0; skipTransformFlagAggregation = isDeclarationFile(file); @@ -190,11 +190,11 @@ namespace ts { symbol.declarations.push(node); if (symbolFlags & SymbolFlags.HasExports && !symbol.exports) { - symbol.exports = createMap(); + symbol.exports = new StringMap(); } if (symbolFlags & SymbolFlags.HasMembers && !symbol.members) { - symbol.members = createMap(); + symbol.members = new StringMap(); } if (symbolFlags & SymbolFlags.Value) { @@ -332,17 +332,17 @@ 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 = symbolTable[name] || (symbolTable[name] = createSymbol(SymbolFlags.None, name)); + symbol = getOrUpdate(symbolTable, name, name => createSymbol(SymbolFlags.None, name)); if (name && (includes & SymbolFlags.Classifiable)) { - classifiableNames[name] = name; + classifiableNames.add(name); } if (symbol.flags & excludes) { if (symbol.isReplaceableByMethod) { // Javascript constructor-declared symbols can be discarded in favor of // prototype symbols like methods. - symbol = symbolTable[name] = createSymbol(SymbolFlags.None, name); + symbol = setAndReturn(symbolTable, name, createSymbol(SymbolFlags.None, name)); } else { if (node.name) { @@ -450,7 +450,7 @@ namespace ts { if (containerFlags & ContainerFlags.IsContainer) { container = blockScopeContainer = node; if (containerFlags & ContainerFlags.HasLocals) { - container.locals = createMap(); + container.locals = new StringMap(); } addToContainerChain(container); } @@ -1447,8 +1447,7 @@ namespace ts { const typeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, "__type"); addDeclarationToSymbol(typeLiteralSymbol, node, SymbolFlags.TypeLiteral); - typeLiteralSymbol.members = createMap(); - typeLiteralSymbol.members[symbol.name] = symbol; + typeLiteralSymbol.members = createMapWithEntry(symbol.name, symbol); } function bindObjectLiteralExpression(node: ObjectLiteralExpression) { @@ -1458,7 +1457,7 @@ namespace ts { } if (inStrictMode) { - const seen = createMap(); + const seen = new StringMap(); for (const prop of node.properties) { if (prop.name.kind !== SyntaxKind.Identifier) { @@ -1479,9 +1478,9 @@ namespace ts { ? ElementKind.Property : ElementKind.Accessor; - const existingKind = seen[identifier.text]; + const existingKind = seen.get(identifier.text); if (!existingKind) { - seen[identifier.text] = currentKind; + seen.set(identifier.text, currentKind); continue; } @@ -1514,7 +1513,7 @@ namespace ts { // fall through. default: if (!blockScopeContainer.locals) { - blockScopeContainer.locals = createMap(); + blockScopeContainer.locals = new StringMap(); addToContainerChain(blockScopeContainer); } declareSymbol(blockScopeContainer.locals, undefined, node, symbolFlags, symbolExcludes); @@ -1976,7 +1975,7 @@ namespace ts { } } - file.symbol.globalExports = file.symbol.globalExports || createMap(); + file.symbol.globalExports = file.symbol.globalExports || new StringMap(); declareSymbol(file.symbol.globalExports, file.symbol, node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); } @@ -2021,7 +2020,7 @@ namespace ts { Debug.assert(isInJavaScriptFile(node)); // Declare a 'member' if the container is an ES5 class or ES6 constructor if (container.kind === SyntaxKind.FunctionDeclaration || container.kind === SyntaxKind.FunctionExpression) { - container.symbol.members = container.symbol.members || createMap(); + container.symbol.members = container.symbol.members || new StringMap(); // 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); } @@ -2053,14 +2052,14 @@ namespace ts { constructorFunction.parent = classPrototype; classPrototype.parent = leftSideOfAssignment; - const funcSymbol = container.locals[constructorFunction.text]; + const funcSymbol = container.locals.get(constructorFunction.text); if (!funcSymbol || !(funcSymbol.flags & SymbolFlags.Function || isDeclarationOfFunctionExpression(funcSymbol))) { return; } // Set up the members collection if it doesn't exist already if (!funcSymbol.members) { - funcSymbol.members = createMap(); + funcSymbol.members = new StringMap(); } // Declare the method/property @@ -2093,7 +2092,7 @@ namespace ts { bindAnonymousDeclaration(node, SymbolFlags.Class, bindingName); // Add name of class expression into the map for semantic classifier if (node.name) { - classifiableNames[node.name.text] = node.name.text; + classifiableNames.add(node.name.text); } } @@ -2109,14 +2108,15 @@ 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 (symbol.exports[prototypeSymbol.name]) { + const symbolExport = symbol.exports.get(prototypeSymbol.name); + if (symbolExport) { if (node.name) { node.name.parent = node; } - file.bindDiagnostics.push(createDiagnosticForNode(symbol.exports[prototypeSymbol.name].declarations[0], + file.bindDiagnostics.push(createDiagnosticForNode(symbolExport.declarations[0], Diagnostics.Duplicate_identifier_0, prototypeSymbol.name)); } - symbol.exports[prototypeSymbol.name] = prototypeSymbol; + symbol.exports.set(prototypeSymbol.name, prototypeSymbol); prototypeSymbol.parent = symbol; } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8fef558968e..941bfdfac8b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -47,7 +47,7 @@ namespace ts { let symbolCount = 0; const emptyArray: any[] = []; - const emptySymbols = createMap(); + const emptySymbols = new StringMap(); const compilerOptions = host.getCompilerOptions(); const languageVersion = compilerOptions.target || ScriptTarget.ES3; @@ -111,10 +111,10 @@ namespace ts { }; const tupleTypes: GenericType[] = []; - const unionTypes = createMap(); - const intersectionTypes = createMap(); - const stringLiteralTypes = createMap(); - const numericLiteralTypes = createMap(); + const unionTypes = new StringMap(); + const intersectionTypes = new StringMap(); + const stringLiteralTypes = new StringMap(); + const numericLiteralTypes = new StringMap(); const unknownSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "unknown"); const resolvingSymbol = createSymbol(SymbolFlags.Transient, "__resolving__"); @@ -137,7 +137,7 @@ namespace ts { const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); const emptyGenericType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); - emptyGenericType.instantiations = createMap(); + emptyGenericType.instantiations = new StringMap(); const anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); // The anyFunctionType contains the anyFunctionType by definition. The flag is further propagated @@ -153,7 +153,7 @@ namespace ts { const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true); - const globals = createMap(); + const globals = new StringMap(); /** * 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. @@ -218,7 +218,7 @@ namespace ts { const mergedSymbols: Symbol[] = []; const symbolLinks: SymbolLinks[] = []; const nodeLinks: NodeLinks[] = []; - const flowLoopCaches: Map[] = []; + const flowLoopCaches: Map[] = []; const flowLoopNodes: FlowNode[] = []; const flowLoopKeys: string[] = []; const flowLoopTypes: Type[][] = []; @@ -292,7 +292,7 @@ namespace ts { NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy, } - const typeofEQFacts = createMap({ + const typeofEQFacts = mapOfMapLike({ "string": TypeFacts.TypeofEQString, "number": TypeFacts.TypeofEQNumber, "boolean": TypeFacts.TypeofEQBoolean, @@ -302,7 +302,7 @@ namespace ts { "function": TypeFacts.TypeofEQFunction }); - const typeofNEFacts = createMap({ + const typeofNEFacts = mapOfMapLike({ "string": TypeFacts.TypeofNEString, "number": TypeFacts.TypeofNENumber, "boolean": TypeFacts.TypeofNEBoolean, @@ -312,7 +312,7 @@ namespace ts { "function": TypeFacts.TypeofNEFunction }); - const typeofTypesByName = createMap({ + const typeofTypesByName = mapOfMapLike({ "string": stringType, "number": numberType, "boolean": booleanType, @@ -322,7 +322,7 @@ namespace ts { let jsxElementType: ObjectType; /** Things we lazy load from the JSX namespace */ - const jsxTypes = createMap(); + const jsxTypes = new StringMap(); const JsxNames = { JSX: "JSX", IntrinsicElements: "IntrinsicElements", @@ -333,11 +333,11 @@ namespace ts { IntrinsicClassAttributes: "IntrinsicClassAttributes" }; - const subtypeRelation = createMap(); - const assignableRelation = createMap(); - const comparableRelation = createMap(); - const identityRelation = createMap(); - const enumRelation = createMap(); + const subtypeRelation = new StringMap(); + const assignableRelation = new StringMap(); + const comparableRelation = new StringMap(); + const identityRelation = new StringMap(); + const enumRelation = new StringMap(); // This is for caching the result of getSymbolDisplayBuilder. Do not access directly. let _displayBuilder: SymbolDisplayBuilder; @@ -351,8 +351,7 @@ namespace ts { ResolvedReturnType } - const builtinGlobals = createMap(); - builtinGlobals[undefinedSymbol.name] = undefinedSymbol; + const builtinGlobals = createMapWithEntry(undefinedSymbol.name, undefinedSymbol); initializeTypeChecker(); @@ -435,11 +434,11 @@ namespace ts { target.declarations.push(node); }); if (source.members) { - if (!target.members) target.members = createMap(); + if (!target.members) target.members = new StringMap(); mergeSymbolTable(target.members, source.members); } if (source.exports) { - if (!target.exports) target.exports = createMap(); + if (!target.exports) target.exports = new StringMap(); mergeSymbolTable(target.exports, source.exports); } recordMergedSymbol(target, source); @@ -457,18 +456,18 @@ namespace ts { } function mergeSymbolTable(target: SymbolTable, source: SymbolTable) { - for (const id in source) { - let targetSymbol = target[id]; + source.forEach((sourceSymbol, id) => { + let targetSymbol = target.get(id); if (!targetSymbol) { - target[id] = source[id]; + target.set(id, sourceSymbol); } else { if (!(targetSymbol.flags & SymbolFlags.Merged)) { - target[id] = targetSymbol = cloneSymbol(targetSymbol); + target.set(id, targetSymbol = cloneSymbol(targetSymbol)); } - mergeSymbol(targetSymbol, source[id]); + mergeSymbol(targetSymbol, sourceSymbol); } - } + }); } function mergeModuleAugmentation(moduleName: LiteralExpression): void { @@ -509,15 +508,16 @@ namespace ts { } function addToSymbolTable(target: SymbolTable, source: SymbolTable, message: DiagnosticMessage) { - for (const id in source) { - if (target[id]) { + source.forEach((sourceSymbol, id) => { + const symbol = target.get(id); + if (symbol) { // Error on redeclarations - forEach(target[id].declarations, addDeclarationDiagnostic(id, message)); + forEach(symbol.declarations, addDeclarationDiagnostic(id, message)); } else { - target[id] = source[id]; + target.set(id, sourceSymbol); } - } + }); function addDeclarationDiagnostic(id: string, message: DiagnosticMessage) { return (declaration: Declaration) => diagnostics.add(createDiagnosticForNode(declaration, message, id)); @@ -541,7 +541,7 @@ namespace ts { function getSymbol(symbols: SymbolTable, name: string, meaning: SymbolFlags): Symbol { if (meaning) { - const symbol = symbols[name]; + const symbol = symbols.get(name); if (symbol) { Debug.assert((symbol.flags & SymbolFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); if (symbol.flags & meaning) { @@ -722,7 +722,7 @@ namespace ts { // It's an external module. First see if the module has an export default and if the local // name of that export default matches. - if (result = moduleExports["default"]) { + if (result = moduleExports.get("default")) { const localSymbol = getLocalSymbolForExportDefault(result); if (localSymbol && (result.flags & meaning) && localSymbol.name === name) { break loop; @@ -741,9 +741,10 @@ 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 (moduleExports[name] && - moduleExports[name].flags === SymbolFlags.Alias && - getDeclarationOfKind(moduleExports[name], SyntaxKind.ExportSpecifier)) { + const moduleExport = moduleExports.get(name); + if (moduleExport && + moduleExport.flags === SymbolFlags.Alias && + getDeclarationOfKind(moduleExport, SyntaxKind.ExportSpecifier)) { break; } } @@ -1058,11 +1059,16 @@ namespace ts { const moduleSymbol = resolveExternalModuleName(node, (node.parent).moduleSpecifier); if (moduleSymbol) { - const exportDefaultSymbol = isShorthandAmbientModuleSymbol(moduleSymbol) ? - moduleSymbol : - moduleSymbol.exports["export="] ? - getPropertyOfType(getTypeOfSymbol(moduleSymbol.exports["export="]), "default") : - resolveSymbol(moduleSymbol.exports["default"]); + let exportDefaultSymbol: Symbol; + if (isShorthandAmbientModuleSymbol(moduleSymbol)) { + exportDefaultSymbol = moduleSymbol; + } + else { + const exportValue = moduleSymbol.exports.get("export="); + exportDefaultSymbol = exportValue + ? getPropertyOfType(getTypeOfSymbol(exportValue), "default") + : resolveSymbol(moduleSymbol.exports.get("default")); + } if (!exportDefaultSymbol && !allowSyntheticDefaultImports) { error(node.name, Diagnostics.Module_0_has_no_default_export, symbolToString(moduleSymbol)); @@ -1112,7 +1118,7 @@ namespace ts { function getExportOfModule(symbol: Symbol, name: string): Symbol { if (symbol.flags & SymbolFlags.Module) { - const exportedSymbol = getExportsOfSymbol(symbol)[name]; + const exportedSymbol = getExportsOfSymbol(symbol).get(name); if (exportedSymbol) { return resolveSymbol(exportedSymbol); } @@ -1140,7 +1146,7 @@ namespace ts { let symbolFromVariable: Symbol; // First check if module was specified with "export=". If so, get the member from the resolved type - if (moduleSymbol && moduleSymbol.exports && moduleSymbol.exports["export="]) { + if (moduleSymbol && moduleSymbol.exports && moduleSymbol.exports.get("export=")) { symbolFromVariable = getPropertyOfType(getTypeOfSymbol(targetSymbol), name.text); } else { @@ -1400,7 +1406,7 @@ namespace ts { // An external module with an 'export =' declaration resolves to the target of the 'export =' declaration, // and an external module with no 'export =' declaration resolves to the module itself. function resolveExternalModuleSymbol(moduleSymbol: Symbol): Symbol { - return moduleSymbol && getMergedSymbol(resolveSymbol(moduleSymbol.exports["export="])) || moduleSymbol; + return moduleSymbol && getMergedSymbol(resolveSymbol(moduleSymbol.exports.get("export="))) || moduleSymbol; } // An external module with an 'export =' declaration may be referenced as an ES6 module provided the 'export =' @@ -1416,7 +1422,7 @@ namespace ts { } function hasExportAssignmentSymbol(moduleSymbol: Symbol): boolean { - return moduleSymbol.exports["export="] !== undefined; + return moduleSymbol.exports.get("export=") !== undefined; } function getExportsOfModuleAsArray(moduleSymbol: Symbol): Symbol[] { @@ -1441,25 +1447,29 @@ namespace ts { * Extends one symbol table with another while collecting information on name collisions for error message generation into the `lookupTable` argument * Not passing `lookupTable` and `exportNode` disables this collection, and just extends the tables */ - function extendExportSymbols(target: SymbolTable, source: SymbolTable, lookupTable?: Map, exportNode?: ExportDeclaration) { - for (const id in source) { - if (id !== "default" && !target[id]) { - target[id] = source[id]; + function extendExportSymbols(target: SymbolTable, source: SymbolTable, lookupTable?: Map, exportNode?: ExportDeclaration) { + if (!source) return; + + source.forEach((sourceSymbol, id) => { + const targetSymbol = target.get(id); + if (id !== "default" && !targetSymbol) { + target.set(id, sourceSymbol); if (lookupTable && exportNode) { - lookupTable[id] = { + lookupTable.set(id, { specifierText: getTextOfNode(exportNode.moduleSpecifier) - } as ExportCollisionTracker; + } as ExportCollisionTracker); } } - else if (lookupTable && exportNode && id !== "default" && target[id] && resolveSymbol(target[id]) !== resolveSymbol(source[id])) { - if (!lookupTable[id].exportsWithDuplicate) { - lookupTable[id].exportsWithDuplicate = [exportNode]; + else if (lookupTable && exportNode && id !== "default" && targetSymbol && resolveSymbol(targetSymbol) !== resolveSymbol(sourceSymbol)) { + const collisionTracker = lookupTable.get(id); + if (!collisionTracker.exportsWithDuplicate) { + collisionTracker.exportsWithDuplicate = [exportNode]; } else { - lookupTable[id].exportsWithDuplicate.push(exportNode); + collisionTracker.exportsWithDuplicate.push(exportNode); } } - } + }); } function getExportsForModule(moduleSymbol: Symbol): SymbolTable { @@ -1475,10 +1485,10 @@ namespace ts { visitedSymbols.push(symbol); const symbols = cloneMap(symbol.exports); // All export * declarations are collected in an __export symbol by the binder - const exportStars = symbol.exports["__export"]; + const exportStars = symbol.exports.get("__export"); if (exportStars) { - const nestedSymbols = createMap(); - const lookupTable = createMap(); + const nestedSymbols = new StringMap(); + const lookupTable = new StringMap(); for (const node of exportStars.declarations) { const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier); const exportedSymbols = visit(resolvedModule); @@ -1489,21 +1499,20 @@ namespace ts { node as ExportDeclaration ); } - for (const id in lookupTable) { - const { exportsWithDuplicate } = lookupTable[id]; + lookupTable.forEach(({ exportsWithDuplicate }, 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) || symbols[id]) { - continue; + if (id === "export=" || !(exportsWithDuplicate && exportsWithDuplicate.length) || symbols.get(id)) { + return; } for (const node of exportsWithDuplicate) { diagnostics.add(createDiagnosticForNode( node, Diagnostics.Module_0_has_already_exported_a_member_named_1_Consider_explicitly_re_exporting_to_resolve_the_ambiguity, - lookupTable[id].specifierText, + lookupTable.get(id).specifierText, id )); } - } + }); extendExportSymbols(symbols, nestedSymbols); } return symbols; @@ -1597,15 +1606,14 @@ namespace ts { function getNamedMembers(members: SymbolTable): Symbol[] { let result: Symbol[]; - for (const id in members) { + members.forEach((symbol, id) => { if (!isReservedMemberName(id)) { if (!result) result = []; - const symbol = members[id]; if (symbolIsValue(symbol)) { result.push(symbol); } } - } + }); return result || emptyArray; } @@ -1678,12 +1686,12 @@ namespace ts { } // If symbol is directly available by its name in the symbol table - if (isAccessible(symbols[symbol.name])) { + if (isAccessible(symbols.get(symbol.name))) { return [symbol]; } // Check if symbol is any of the alias - return forEachProperty(symbols, symbolFromSymbolTable => { + return findInMap(symbols, symbolFromSymbolTable => { if (symbolFromSymbolTable.flags & SymbolFlags.Alias && symbolFromSymbolTable.name !== "export=" && !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)) { @@ -1718,7 +1726,7 @@ namespace ts { let qualify = false; forEachSymbolTableInScope(enclosingDeclaration, symbolTable => { // If symbol of this name is not available in the symbol table we are ok - let symbolFromSymbolTable = symbolTable[symbol.name]; + let symbolFromSymbolTable = symbolTable.get(symbol.name); if (!symbolFromSymbolTable) { // Continue to the next symbol table return false; @@ -2455,7 +2463,7 @@ namespace ts { } writeIndexSignature(resolved.stringIndexInfo, SyntaxKind.StringKeyword); writeIndexSignature(resolved.numberIndexInfo, SyntaxKind.NumberKeyword); - for (const p of resolved.properties) { + for (const p of sortInV8ObjectInsertionOrder(resolved.properties, p => p.name)) { const t = getTypeOfSymbol(p); if (p.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(t).length) { const signatures = getSignaturesOfType(t, SignatureKind.Call); @@ -3162,7 +3170,7 @@ namespace ts { // Return the type implied by an object binding pattern function getTypeFromObjectBindingPattern(pattern: ObjectBindingPattern, includePatternInType: boolean, reportErrors: boolean): Type { - const members = createMap(); + const members = new StringMap(); let hasComputedProperties = false; forEach(pattern.elements, e => { const name = e.propertyName || e.name; @@ -3177,7 +3185,7 @@ namespace ts { const symbol = createSymbol(flags, text); symbol.type = getTypeFromBindingElement(e, includePatternInType, reportErrors); symbol.bindingElement = e; - members[symbol.name] = symbol; + members.set(symbol.name, symbol); }); const result = createAnonymousType(undefined, members, emptyArray, emptyArray, undefined, undefined); if (includePatternInType) { @@ -3767,8 +3775,7 @@ namespace ts { type.typeParameters = concatenate(outerTypeParameters, localTypeParameters); type.outerTypeParameters = outerTypeParameters; type.localTypeParameters = localTypeParameters; - (type).instantiations = createMap(); - (type).instantiations[getTypeListId(type.typeParameters)] = type; + (type).instantiations = createMapWithEntry(getTypeListId(type.typeParameters), type); (type).target = type; (type).typeArguments = type.typeParameters; type.thisType = createType(TypeFlags.TypeParameter); @@ -3810,8 +3817,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 = createMap(); - links.instantiations[getTypeListId(links.typeParameters)] = type; + links.instantiations = createMapWithEntry(getTypeListId(links.typeParameters), type); } } else { @@ -3831,7 +3837,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 && !!symbol.exports[(expr).text]; + expr.kind === SyntaxKind.Identifier && !!symbol.exports.get((expr).text); } function enumHasLiteralMembers(symbol: Symbol) { @@ -3862,16 +3868,15 @@ namespace ts { enumType.symbol = symbol; if (enumHasLiteralMembers(symbol)) { const memberTypeList: Type[] = []; - const memberTypes = createMap(); + const memberTypes = new NumberMap(); for (const declaration of enumType.symbol.declarations) { if (declaration.kind === SyntaxKind.EnumDeclaration) { computeEnumMemberValues(declaration); for (const member of (declaration).members) { const memberSymbol = getSymbolOfNode(member); const value = getEnumMemberValue(member); - if (!memberTypes[value]) { - const memberType = memberTypes[value] = createEnumLiteralType(memberSymbol, enumType, "" + value); - memberTypeList.push(memberType); + if (!memberTypes.get(value)) { + memberTypeList.push(setAndReturn(memberTypes, value, createEnumLiteralType(memberSymbol, enumType, "" + value))); } } } @@ -3880,7 +3885,7 @@ namespace ts { if (memberTypeList.length > 1) { enumType.flags |= TypeFlags.Union; (enumType).types = memberTypeList; - unionTypes[getTypeListId(memberTypeList)] = enumType; + unionTypes.set(getTypeListId(memberTypeList), enumType); } } } @@ -3892,7 +3897,7 @@ namespace ts { if (!links.declaredType) { const enumType = getDeclaredTypeOfEnum(getParentOfSymbol(symbol)); links.declaredType = enumType.flags & TypeFlags.Union ? - enumType.memberTypes[getEnumMemberValue(symbol.valueDeclaration)] : + enumType.memberTypes.get(getEnumMemberValue(symbol.valueDeclaration)) : enumType; } return links.declaredType; @@ -4022,27 +4027,19 @@ namespace ts { } function createSymbolTable(symbols: Symbol[]): SymbolTable { - const result = createMap(); - for (const symbol of symbols) { - result[symbol.name] = symbol; - } - return result; + return arrayToMap(symbols, symbol => symbol.name); } // 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 = createMap(); - for (const symbol of symbols) { - result[symbol.name] = mappingThisOnly && isIndependentMember(symbol) ? symbol : instantiateSymbol(symbol, mapper); - } - return result; + return arrayToMap(symbols, symbol => symbol.name, symbol => mappingThisOnly && isIndependentMember(symbol) ? symbol : instantiateSymbol(symbol, mapper)); } function addInheritedMembers(symbols: SymbolTable, baseSymbols: Symbol[]) { for (const s of baseSymbols) { - if (!symbols[s.name]) { - symbols[s.name] = s; + if (!symbols.get(s.name)) { + symbols.set(s.name, s); } } } @@ -4051,8 +4048,8 @@ namespace ts { if (!(type).declaredProperties) { const symbol = type.symbol; (type).declaredProperties = getNamedMembers(symbol.members); - (type).declaredCallSignatures = getSignaturesOfSymbol(symbol.members["__call"]); - (type).declaredConstructSignatures = getSignaturesOfSymbol(symbol.members["__new"]); + (type).declaredCallSignatures = getSignaturesOfSymbol(symbol.members.get("__call")); + (type).declaredConstructSignatures = getSignaturesOfSymbol(symbol.members.get("__new")); (type).declaredStringIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.String); (type).declaredNumberIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.Number); } @@ -4292,8 +4289,8 @@ namespace ts { } else if (symbol.flags & SymbolFlags.TypeLiteral) { const members = symbol.members; - const callSignatures = getSignaturesOfSymbol(members["__call"]); - const constructSignatures = getSignaturesOfSymbol(members["__new"]); + const callSignatures = getSignaturesOfSymbol(members.get("__call")); + const constructSignatures = getSignaturesOfSymbol(members.get("__new")); const stringIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.String); const numberIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.Number); setObjectTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); @@ -4307,7 +4304,7 @@ namespace ts { } if (symbol.flags & SymbolFlags.Class) { const classType = getDeclaredTypeOfClassOrInterface(symbol); - constructSignatures = getSignaturesOfSymbol(symbol.members["__constructor"]); + constructSignatures = getSignaturesOfSymbol(symbol.members.get("__constructor")); if (!constructSignatures.length) { constructSignatures = getDefaultConstructSignatures(classType); } @@ -4363,7 +4360,7 @@ namespace ts { function getPropertyOfObjectType(type: Type, name: string): Symbol { if (type.flags & TypeFlags.ObjectType) { const resolved = resolveStructuredTypeMembers(type); - const symbol = resolved.members[name]; + const symbol = resolved.members.get(name); if (symbol && symbolIsValue(symbol)) { return symbol; } @@ -4384,13 +4381,12 @@ namespace ts { const props = type.resolvedProperties; if (props) { const result: Symbol[] = []; - for (const key in props) { - const prop = props[key]; + props.forEach((prop, key) => { // We need to filter out partial properties in union types if (!(prop.flags & SymbolFlags.SyntheticProperty && (prop).isPartial)) { result.push(prop); } - } + }); return result; } return emptyArray; @@ -4507,12 +4503,12 @@ namespace ts { // these partial properties when identifying discriminant properties, but otherwise they are filtered out // and do not appear to be present in the union type. function getUnionOrIntersectionProperty(type: UnionOrIntersectionType, name: string): Symbol { - const properties = type.resolvedProperties || (type.resolvedProperties = createMap()); - let property = properties[name]; + const properties = type.resolvedProperties || (type.resolvedProperties = new StringMap()); + let property = properties.get(name); if (!property) { property = createUnionOrIntersectionProperty(type, name); if (property) { - properties[name] = property; + properties.set(name, property); } } return property; @@ -4536,7 +4532,7 @@ namespace ts { type = getApparentType(type); if (type.flags & TypeFlags.ObjectType) { const resolved = resolveStructuredTypeMembers(type); - const symbol = resolved.members[name]; + const symbol = resolved.members.get(name); if (symbol && symbolIsValue(symbol)) { return symbol; } @@ -4635,11 +4631,11 @@ namespace ts { function symbolsToArray(symbols: SymbolTable): Symbol[] { const result: Symbol[] = []; - for (const id in symbols) { + symbols.forEach((symbol, id) => { if (!isReservedMemberName(id)) { - result.push(symbols[id]); + result.push(symbol); } - } + }); return result; } @@ -4934,7 +4930,7 @@ namespace ts { } function getIndexSymbol(symbol: Symbol): Symbol { - return symbol.members["__index"]; + return symbol.members.get("__index"); } function getIndexDeclarationOfSymbol(symbol: Symbol, kind: IndexKind): SignatureDeclaration { @@ -5048,11 +5044,11 @@ namespace ts { function createTypeReference(target: GenericType, typeArguments: Type[]): TypeReference { const id = getTypeListId(typeArguments); - let type = target.instantiations[id]; + let type = target.instantiations.get(id); if (!type) { const propagatedFlags = typeArguments ? getPropagatingFlagsOfTypes(typeArguments, /*excludeKinds*/ 0) : 0; const flags = TypeFlags.Reference | propagatedFlags; - type = target.instantiations[id] = createObjectType(flags, target.symbol); + type = setAndReturn(target.instantiations, id, createObjectType(flags, target.symbol)); type.target = target; type.typeArguments = typeArguments; } @@ -5105,7 +5101,7 @@ namespace ts { } const typeArguments = map(node.typeArguments, getTypeFromTypeNodeNoAlias); const id = getTypeListId(typeArguments); - return links.instantiations[id] || (links.instantiations[id] = instantiateType(type, createTypeMapper(typeParameters, typeArguments))); + return links.instantiations.get(id) || setAndReturn(links.instantiations, id, instantiateType(type, createTypeMapper(typeParameters, typeArguments))); } if (node.typeArguments) { error(node, Diagnostics.Type_0_is_not_generic, symbolToString(symbol)); @@ -5333,8 +5329,7 @@ namespace ts { type.typeParameters = typeParameters; type.outerTypeParameters = undefined; type.localTypeParameters = typeParameters; - type.instantiations = createMap(); - type.instantiations[getTypeListId(type.typeParameters)] = type; + type.instantiations = createMapWithEntry(getTypeListId(type.typeParameters), type); type.target = type; type.typeArguments = type.typeParameters; type.thisType = createType(TypeFlags.TypeParameter); @@ -5537,10 +5532,10 @@ namespace ts { return types[0]; } const id = getTypeListId(types); - let type = unionTypes[id]; + let type = unionTypes.get(id); if (!type) { const propagatedFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); - type = unionTypes[id] = createObjectType(TypeFlags.Union | propagatedFlags); + type = setAndReturn(unionTypes, id, createObjectType(TypeFlags.Union | propagatedFlags)); type.types = types; type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = aliasTypeArguments; @@ -5594,10 +5589,10 @@ namespace ts { return typeSet[0]; } const id = getTypeListId(typeSet); - let type = intersectionTypes[id]; + let type = intersectionTypes.get(id); if (!type) { const propagatedFlags = getPropagatingFlagsOfTypes(typeSet, /*excludeKinds*/ TypeFlags.Nullable); - type = intersectionTypes[id] = createObjectType(TypeFlags.Intersection | propagatedFlags); + type = setAndReturn(intersectionTypes, id, createObjectType(TypeFlags.Intersection | propagatedFlags)); type.types = typeSet; type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = aliasTypeArguments; @@ -5649,7 +5644,7 @@ namespace ts { function getLiteralTypeForText(flags: TypeFlags, text: string) { const map = flags & TypeFlags.StringLiteral ? stringLiteralTypes : numericLiteralTypes; - return map[text] || (map[text] = createLiteralType(flags, text)); + return map.get(text) || setAndReturn(map, text, createLiteralType(flags, text)); } function getTypeFromLiteralTypeNode(node: LiteralTypeNode): Type { @@ -6328,13 +6323,14 @@ namespace ts { return true; } const id = source.id + "," + target.id; - if (enumRelation[id] !== undefined) { - return enumRelation[id]; + const relation = enumRelation.get(id); + if (relation !== undefined) { + return relation; } if (source.symbol.name !== target.symbol.name || !(source.symbol.flags & SymbolFlags.RegularEnum) || !(target.symbol.flags & SymbolFlags.RegularEnum) || (source.flags & TypeFlags.Union) !== (target.flags & TypeFlags.Union)) { - return enumRelation[id] = false; + return setAndReturn(enumRelation, id, false); } const targetEnumType = getTypeOfSymbol(target.symbol); for (const property of getPropertiesOfType(getTypeOfSymbol(source.symbol))) { @@ -6345,14 +6341,14 @@ namespace ts { errorReporter(Diagnostics.Property_0_is_missing_in_type_1, property.name, typeToString(target, /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType)); } - return enumRelation[id] = false; + return setAndReturn(enumRelation, id, false); } } } - return enumRelation[id] = true; + return setAndReturn(enumRelation, id, true); } - function isSimpleTypeRelatedTo(source: Type, target: Type, relation: Map, errorReporter?: ErrorReporter) { + function isSimpleTypeRelatedTo(source: Type, target: Type, relation: Map, errorReporter?: ErrorReporter) { if (target.flags & TypeFlags.Never) return false; if (target.flags & TypeFlags.Any || source.flags & TypeFlags.Never) return true; if (source.flags & TypeFlags.StringLike && target.flags & TypeFlags.String) return true; @@ -6380,7 +6376,7 @@ namespace ts { return false; } - function isTypeRelatedTo(source: Type, target: Type, relation: Map) { + function isTypeRelatedTo(source: Type, target: Type, relation: Map) { if (source.flags & TypeFlags.StringOrNumberLiteral && source.flags & TypeFlags.FreshLiteral) { source = (source).regularType; } @@ -6392,7 +6388,7 @@ namespace ts { } if (source.flags & TypeFlags.ObjectType && target.flags & TypeFlags.ObjectType) { const id = relation !== identityRelation || source.id < target.id ? source.id + "," + target.id : target.id + "," + source.id; - const related = relation[id]; + const related = relation.get(id); if (related !== undefined) { return related === RelationComparisonResult.Succeeded; } @@ -6416,7 +6412,7 @@ namespace ts { function checkTypeRelatedTo( source: Type, target: Type, - relation: Map, + relation: Map, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: DiagnosticMessageChain): boolean { @@ -6424,7 +6420,7 @@ namespace ts { let errorInfo: DiagnosticMessageChain; let sourceStack: ObjectType[]; let targetStack: ObjectType[]; - let maybeStack: Map[]; + let maybeStack: Map[]; let expandingFlags: number; let depth = 0; let overflow = false; @@ -6790,12 +6786,12 @@ namespace ts { return Ternary.False; } const id = relation !== identityRelation || source.id < target.id ? source.id + "," + target.id : target.id + "," + source.id; - const related = relation[id]; + const related = relation.get(id); if (related !== undefined) { if (reportErrors && related === RelationComparisonResult.Failed) { // We are elaborating errors and the cached result is an unreported failure. Record the result as a reported // failure and continue computing the relation such that errors get reported. - relation[id] = RelationComparisonResult.FailedAndReported; + relation.set(id, RelationComparisonResult.FailedAndReported); } else { return related === RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False; @@ -6804,7 +6800,7 @@ namespace ts { if (depth > 0) { for (let i = 0; i < depth; i++) { // If source and target are already being compared, consider them related with assumptions - if (maybeStack[i][id]) { + if (maybeStack[i].get(id)) { return Ternary.Maybe; } } @@ -6821,8 +6817,7 @@ namespace ts { } sourceStack[depth] = source; targetStack[depth] = target; - maybeStack[depth] = createMap(); - maybeStack[depth][id] = RelationComparisonResult.Succeeded; + maybeStack[depth] = createMapWithEntry(id, RelationComparisonResult.Succeeded); depth++; const saveExpandingFlags = expandingFlags; if (!(expandingFlags & 1) && isDeeplyNestedGeneric(source, sourceStack, depth)) expandingFlags |= 1; @@ -6852,12 +6847,12 @@ 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]; - copyProperties(maybeCache, destinationCache); + copyMapEntriesFromTo(maybeCache, destinationCache); } else { // A false result goes straight into global cache (when something is false under assumptions it // will also be false without assumptions) - relation[id] = reportErrors ? RelationComparisonResult.FailedAndReported : RelationComparisonResult.Failed; + relation.set(id, reportErrors ? RelationComparisonResult.FailedAndReported : RelationComparisonResult.Failed); } return result; } @@ -7046,14 +7041,29 @@ namespace ts { return result; } - function eachPropertyRelatedTo(source: Type, target: Type, kind: IndexKind, reportErrors: boolean): Ternary { + /** + * For consistency we report the first error in V8 object insertion order. + * Since that's slow and there usually isn't an error, we only sort properties the second time around. + */ + function eachPropertyRelatedTo(source: Type, target: Type, kind: IndexKind, reportErrors: boolean, redoingInV8ObjectInsertionOrder?: boolean): Ternary { let result = Ternary.True; - for (const prop of getPropertiesOfObjectType(source)) { + let properties = getPropertiesOfObjectType(source); + if (redoingInV8ObjectInsertionOrder) { + properties = sortInV8ObjectInsertionOrder(properties, prop => prop.name); + } + for (const prop of properties) { if (kind === IndexKind.String || isNumericLiteralName(prop.name)) { - const related = isRelatedTo(getTypeOfSymbol(prop), target, reportErrors); + const related = isRelatedTo(getTypeOfSymbol(prop), target, reportErrors && redoingInV8ObjectInsertionOrder); if (!related) { if (reportErrors) { - reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop)); + // For consistency, if we report errors we make sure to report the first error in V8's object insertion order. + if (!redoingInV8ObjectInsertionOrder) { + const related = eachPropertyRelatedTo(source, target, kind, reportErrors, /*redoingInV8ObjectInsertionOrder*/ true); + Debug.assert(related === Ternary.False); + } + else { + reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop)); + } } return Ternary.False; } @@ -7492,11 +7502,11 @@ namespace ts { } function transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) { - const members = createMap(); + const members = new StringMap(); for (const property of getPropertiesOfObjectType(type)) { const original = getTypeOfSymbol(property); const updated = f(original); - members[property.name] = updated === original ? property : createTransientSymbol(property, updated); + members.set(property.name, updated === original ? property : createTransientSymbol(property, updated)); }; return members; } @@ -7714,7 +7724,7 @@ namespace ts { let targetStack: Type[]; let depth = 0; let inferiority = 0; - const visited = createMap(); + const visited = new StringSet(); inferFromTypes(originalSource, originalTarget); function isInProcess(source: Type, target: Type) { @@ -7850,10 +7860,10 @@ namespace ts { return; } const key = source.id + "," + target.id; - if (visited[key]) { + if (visited.has(key)) { return; } - visited[key] = true; + visited.add(key); if (depth === 0) { sourceStack = []; targetStack = []; @@ -8204,7 +8214,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 || - resolved.members["bind"] && isTypeSubtypeOf(type, globalFunctionType)); + resolved.members.get("bind") && isTypeSubtypeOf(type, globalFunctionType)); } function getTypeFacts(type: Type): TypeFacts { @@ -8617,12 +8627,13 @@ 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] = createMap()); + const cache = flowLoopCaches[id] || (flowLoopCaches[id] = new StringMap()); if (!key) { key = getFlowCacheKey(reference); } - if (cache[key]) { - return cache[key]; + const cached = cache.get(key); + if (cached) { + return cached; } // If this flow loop junction and reference are already being processed, return // the union of the types computed for each branch so far, marked as incomplete. @@ -8652,8 +8663,9 @@ namespace ts { // If we see a value appear in the cache it is a sign that control flow analysis // was restarted and completed by checkExpressionCached. We can simply pick up // the resulting type and bail out. - if (cache[key]) { - return cache[key]; + const cached = cache.get(key); + if (cached) { + return cached; } if (!contains(antecedentTypes, type)) { antecedentTypes.push(type); @@ -8677,7 +8689,7 @@ namespace ts { if (isIncomplete(firstAntecedentType)) { return createFlowType(result, /*incomplete*/ true); } - return cache[key] = result; + return setAndReturn(cache, key, result); } function isMatchingReferenceDiscriminant(expr: Expression) { @@ -8800,14 +8812,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 = typeofTypesByName[literal.text]; + const targetType = typeofTypesByName.get(literal.text); if (targetType && isTypeSubtypeOf(targetType, type)) { return targetType; } } const facts = assumeTrue ? - typeofEQFacts[literal.text] || TypeFacts.TypeofEQHostObject : - typeofNEFacts[literal.text] || TypeFacts.TypeofNEHostObject; + typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject : + typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject; return getTypeWithFacts(type, facts); } @@ -10293,7 +10305,7 @@ namespace ts { // Grammar checking checkGrammarObjectLiteralExpression(node, inDestructuringPattern); - const propertiesTable = createMap(); + const propertiesTable = new StringMap(); const propertiesArray: Symbol[] = []; const contextualType = getApparentTypeOfContextualType(node); const contextualTypeHasPattern = contextualType && contextualType.pattern && @@ -10376,7 +10388,7 @@ namespace ts { } } else { - propertiesTable[member.name] = member; + propertiesTable.set(member.name, member); } propertiesArray.push(member); } @@ -10385,12 +10397,12 @@ namespace ts { // type with those properties for which the binding pattern specifies a default value. if (contextualTypeHasPattern) { for (const prop of getPropertiesOfType(contextualType)) { - if (!propertiesTable[prop.name]) { + if (!propertiesTable.get(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); } - propertiesTable[prop.name] = prop; + propertiesTable.set(prop.name, prop); propertiesArray.push(prop); } } @@ -10466,7 +10478,7 @@ namespace ts { } } - function checkJsxAttribute(node: JsxAttribute, elementAttributesType: Type, nameTable: Map) { + function checkJsxAttribute(node: JsxAttribute, elementAttributesType: Type, nameTable: Set) { let correspondingPropType: Type = undefined; // Look up the corresponding property for this attribute @@ -10505,34 +10517,35 @@ namespace ts { checkTypeAssignableTo(exprType, correspondingPropType, node); } - nameTable[node.name.text] = true; + nameTable.add(node.name.text); return exprType; } - function checkJsxSpreadAttribute(node: JsxSpreadAttribute, elementAttributesType: Type, nameTable: Map) { + function checkJsxSpreadAttribute(node: JsxSpreadAttribute, elementAttributesType: Type, nameTable: Set) { const type = checkExpression(node.expression); const props = getPropertiesOfType(type); 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 (!nameTable[prop.name]) { + if (!nameTable.has(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); checkTypeAssignableTo(getTypeOfSymbol(prop), getTypeOfSymbol(targetPropSym), node, undefined, msg); } - nameTable[prop.name] = true; + nameTable.add(prop.name); } } return type; } function getJsxType(name: string) { - if (jsxTypes[name] === undefined) { - return jsxTypes[name] = getExportedTypeFromNamespace(JsxNames.JSX, name) || unknownType; + const jsxType = jsxTypes.get(name); + if (jsxType === undefined) { + return setAndReturn(jsxTypes, name, getExportedTypeFromNamespace(JsxNames.JSX, name) || unknownType); } - return jsxTypes[name]; + return jsxType; } /** @@ -10837,7 +10850,7 @@ namespace ts { const targetAttributesType = getJsxElementAttributesType(node); - const nameTable = createMap(); + const nameTable = new StringSet(); // 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 @@ -10861,7 +10874,7 @@ namespace ts { const targetProperties = getPropertiesOfType(targetAttributesType); for (let i = 0; i < targetProperties.length; i++) { if (!(targetProperties[i].flags & SymbolFlags.Optional) && - !nameTable[targetProperties[i].name]) { + !nameTable.has(targetProperties[i].name)) { error(node, Diagnostics.Property_0_is_missing_in_type_1, targetProperties[i].name, typeToString(targetAttributesType)); } @@ -11591,7 +11604,7 @@ namespace ts { return typeArgumentsAreAssignable; } - function checkApplicableSignature(node: CallLikeExpression, args: Expression[], signature: Signature, relation: Map, excludeArgument: boolean[], reportErrors: boolean) { + function checkApplicableSignature(node: CallLikeExpression, args: Expression[], signature: Signature, relation: Map, excludeArgument: boolean[], reportErrors: boolean) { const thisType = getThisTypeOfSignature(signature); if (thisType && thisType !== voidType && node.kind !== SyntaxKind.NewExpression) { // If the called expression is not of the form `x.f` or `x["f"]`, then sourceType = voidType @@ -12125,7 +12138,7 @@ namespace ts { diagnostics.add(createDiagnosticForNodeFromMessageChain(node, errorInfo)); } - function chooseOverload(candidates: Signature[], relation: Map, signatureHelpTrailingComma = false) { + function chooseOverload(candidates: Signature[], relation: Map, signatureHelpTrailingComma = false) { for (const originalCandidate of candidates) { if (!hasCorrectArity(node, args, originalCandidate, signatureHelpTrailingComma)) { continue; @@ -14271,8 +14284,8 @@ namespace ts { Property = Getter | Setter } - const instanceNames = createMap(); - const staticNames = createMap(); + const instanceNames = new StringMap(); + const staticNames = new StringMap(); for (const member of node.members) { if (member.kind === SyntaxKind.Constructor) { for (const param of (member as ConstructorDeclaration).parameters) { @@ -14304,24 +14317,24 @@ namespace ts { } } - function addName(names: Map, location: Node, name: string, meaning: Accessor) { - const prev = names[name]; + function addName(names: Map, location: Node, name: string, meaning: Accessor) { + const prev = names.get(name); if (prev) { if (prev & meaning) { error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); } else { - names[name] = prev | meaning; + names.set(name, prev | meaning); } } else { - names[name] = meaning; + names.set(name, meaning); } } } function checkObjectTypeForDuplicateDeclarations(node: TypeLiteralNode | InterfaceDeclaration) { - const names = createMap(); + const names = new StringSet(); for (const member of node.members) { if (member.kind == SyntaxKind.PropertySignature) { let memberName: string; @@ -14335,12 +14348,12 @@ namespace ts { continue; } - if (names[memberName]) { + if (names.has(memberName)) { error(member.symbol.valueDeclaration.name, Diagnostics.Duplicate_identifier_0, memberName); error(member.name, Diagnostics.Duplicate_identifier_0, memberName); } else { - names[memberName] = true; + names.add(memberName); } } } @@ -15545,8 +15558,7 @@ namespace ts { function checkUnusedLocalsAndParameters(node: Node): void { if (node.parent.kind !== SyntaxKind.InterfaceDeclaration && noUnusedIdentifiers && !isInAmbientContext(node)) { - for (const key in node.locals) { - const local = node.locals[key]; + node.locals.forEach(local => { if (!local.isReferenced) { if (local.valueDeclaration && local.valueDeclaration.kind === SyntaxKind.Parameter) { const parameter = local.valueDeclaration; @@ -15561,7 +15573,7 @@ namespace ts { forEach(local.declarations, d => error(d.name || d, Diagnostics._0_is_declared_but_never_used, local.name)); } } - } + }); } } @@ -15611,8 +15623,7 @@ namespace ts { function checkUnusedModuleMembers(node: ModuleDeclaration | SourceFile): void { if (compilerOptions.noUnusedLocals && !isInAmbientContext(node)) { - for (const key in node.locals) { - const local = node.locals[key]; + node.locals.forEach(local => { if (!local.isReferenced && !local.exportSymbol) { for (const declaration of local.declarations) { if (!isAmbientModule(declaration)) { @@ -15620,7 +15631,7 @@ namespace ts { } } } - } + }); } } @@ -16637,7 +16648,7 @@ namespace ts { const identifierName = (catchClause.variableDeclaration.name).text; const locals = catchClause.block.locals; if (locals) { - const localSymbol = locals[identifierName]; + const localSymbol = locals.get(identifierName); if (localSymbol && (localSymbol.flags & SymbolFlags.BlockScopedVariable) !== 0) { grammarErrorOnNode(localSymbol.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, identifierName); } @@ -17057,16 +17068,15 @@ namespace ts { return true; } - const seen = createMap<{ prop: Symbol; containingType: Type }>(); - forEach(resolveDeclaredMembers(type).declaredProperties, p => { seen[p.name] = { prop: p, containingType: type }; }); + const seen: Map = arrayToMap(resolveDeclaredMembers(type).declaredProperties, p => p.name, p => ({ prop: p, containingType: type })); let ok = true; for (const base of baseTypes) { const properties = getPropertiesOfObjectType(getTypeWithThisArgument(base, type.thisType)); for (const prop of properties) { - const existing = seen[prop.name]; + const existing = seen.get(prop.name); if (!existing) { - seen[prop.name] = { prop: prop, containingType: base }; + seen.set(prop.name, { prop, containingType: base }); } else { const isInheritedProperty = existing.containingType !== type; @@ -17804,19 +17814,14 @@ namespace ts { } function hasExportedMembers(moduleSymbol: Symbol) { - for (const id in moduleSymbol.exports) { - if (id !== "export=") { - return true; - } - } - return false; + return someKeyInMap(moduleSymbol.exports, id => id !== "export="); } function checkExternalModuleExports(node: SourceFile | ModuleDeclaration) { const moduleSymbol = getSymbolOfNode(node); const links = getSymbolLinks(moduleSymbol); if (!links.exportsChecked) { - const exportEqualsSymbol = moduleSymbol.exports["export="]; + const exportEqualsSymbol = moduleSymbol.exports.get("export="); if (exportEqualsSymbol && hasExportedMembers(moduleSymbol)) { const declaration = getDeclarationOfAliasSymbol(exportEqualsSymbol) || exportEqualsSymbol.valueDeclaration; if (!isTopLevelInExternalModuleAugmentation(declaration)) { @@ -17825,21 +17830,20 @@ namespace ts { } // Checks for export * conflicts const exports = getExportsOfModule(moduleSymbol); - for (const id in exports) { + exports.forEach(({ declarations, flags }, id) => { if (id === "__export") { - continue; + return; } - const { declarations, flags } = exports[id]; // ECMA262: 15.2.1.1 It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries. // (TS Exceptions: namespaces, function overloads, enums, and interfaces) if (flags & (SymbolFlags.Namespace | SymbolFlags.Interface | SymbolFlags.Enum)) { - continue; + return; } const exportedDeclarationsCount = countWhere(declarations, isNotOverload); if (flags & SymbolFlags.TypeAlias && exportedDeclarationsCount <= 2) { // it is legal to merge type alias with other values // so count should be either 1 (just type alias) or 2 (type alias + merged value) - continue; + return; } if (exportedDeclarationsCount > 1) { for (const declaration of declarations) { @@ -17848,7 +17852,7 @@ namespace ts { } } } - } + }); links.exportsChecked = true; } @@ -18123,7 +18127,7 @@ namespace ts { } function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] { - const symbols = createMap(); + const symbols = new StringMap(); let memberFlags: ModifierFlags = ModifierFlags.None; if (isInsideWithStatementBody(location)) { @@ -18201,18 +18205,17 @@ 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 (!symbols[id]) { - symbols[id] = symbol; + if (!symbols.get(id)) { + symbols.set(id, symbol); } } } function copySymbols(source: SymbolTable, meaning: SymbolFlags): void { if (meaning) { - for (const id in source) { - const symbol = source[id]; + source.forEach((symbol, id) => { copySymbol(symbol, meaning); - } + }); } } } @@ -18623,8 +18626,8 @@ namespace ts { const propsByName = createSymbolTable(getPropertiesOfType(type)); if (getSignaturesOfType(type, SignatureKind.Call).length || getSignaturesOfType(type, SignatureKind.Construct).length) { forEach(getPropertiesOfType(globalFunctionType), p => { - if (!propsByName[p.name]) { - propsByName[p.name] = p; + if (!propsByName.get(p.name)) { + propsByName.set(p.name, p); } }); } @@ -18687,7 +18690,7 @@ namespace ts { // otherwise - check if at least one export is value symbolLinks.exportsSomeValue = hasExportAssignment ? !!(moduleSymbol.flags & SymbolFlags.Value) - : forEachProperty(getExportsOfModule(moduleSymbol), isValue); + : someValueInMap(getExportsOfModule(moduleSymbol), isValue); } return symbolLinks.exportsSomeValue; @@ -19037,7 +19040,7 @@ namespace ts { } function hasGlobalName(name: string): boolean { - return !!globals[name]; + return !!globals.get(name); } function getReferencedValueSymbol(reference: Identifier, startInDeclarationContainer?: boolean): Symbol { @@ -19094,14 +19097,13 @@ namespace ts { if (resolvedTypeReferenceDirectives) { // populate reverse mapping: file path -> type reference directive that was resolved to this file fileToDirective = createFileMap(); - for (const key in resolvedTypeReferenceDirectives) { - const resolvedDirective = resolvedTypeReferenceDirectives[key]; + resolvedTypeReferenceDirectives.forEach((resolvedDirective, key) => { if (!resolvedDirective) { - continue; + return; } const file = host.getSourceFile(resolvedDirective.resolvedFileName); fileToDirective.set(file.path, key); - } + }); } return { getReferencedExportContainer, @@ -19242,11 +19244,9 @@ namespace ts { if (file.symbol && file.symbol.globalExports) { // Merge in UMD exports with first-in-wins semantics (see #9771) const source = file.symbol.globalExports; - for (const id in source) { - if (!(id in globals)) { - globals[id] = source[id]; - } - } + source.forEach((sourceSymbol, id) => { + setIfNotSet(globals, id, sourceSymbol); + }); } if ((compilerOptions.isolatedModules || isExternalModule(file)) && !file.isDeclarationFile) { const fileRequestedExternalEmitHelpers = file.flags & NodeFlags.EmitHelperFlags; @@ -19943,7 +19943,7 @@ namespace ts { } function checkGrammarObjectLiteralExpression(node: ObjectLiteralExpression, inDestructuring: boolean) { - const seen = createMap(); + const seen = new StringMap(); const Property = 1; const GetAccessor = 2; const SetAccessor = 4; @@ -20007,17 +20007,17 @@ namespace ts { continue; } - if (!seen[effectiveName]) { - seen[effectiveName] = currentKind; + const existingKind = seen.get(effectiveName); + if (!existingKind) { + seen.set(effectiveName, currentKind); } else { - const existingKind = seen[effectiveName]; if (currentKind === Property && existingKind === Property) { grammarErrorOnNode(name, Diagnostics.Duplicate_identifier_0, getTextOfNode(name)); } else if ((currentKind & GetOrSetAccessor) && (existingKind & GetOrSetAccessor)) { if (existingKind !== GetOrSetAccessor && currentKind !== existingKind) { - seen[effectiveName] = currentKind | existingKind; + seen.set(effectiveName, currentKind | existingKind); } else { return grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_multiple_get_Slashset_accessors_with_the_same_name); @@ -20031,7 +20031,7 @@ namespace ts { } function checkGrammarJsxElement(node: JsxOpeningLikeElement) { - const seen = createMap(); + const seen = new StringSet(); for (const attr of node.attributes) { if (attr.kind === SyntaxKind.JsxSpreadAttribute) { continue; @@ -20039,8 +20039,8 @@ namespace ts { const jsxAttr = (attr); const name = jsxAttr.name; - if (!seen[name.text]) { - seen[name.text] = true; + if (!seen.has(name.text)) { + seen.add(name.text); } else { return grammarErrorOnNode(name, Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name); @@ -20534,11 +20534,11 @@ namespace ts { function getAmbientModules(): Symbol[] { const result: Symbol[] = []; - for (const sym in globals) { + globals.forEach((global, sym) => { if (ambientModuleSymbolRegex.test(sym)) { - result.push(globals[sym]); + result.push(global); } - } + }); return result; } } diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 648447ac26a..12e36f7534a 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -65,7 +65,7 @@ namespace ts { }, { name: "jsx", - type: createMap({ + type: mapOfMapLike({ "preserve": JsxEmit.Preserve, "react": JsxEmit.React }), @@ -95,7 +95,7 @@ namespace ts { { name: "module", shortName: "m", - type: createMap({ + type: mapOfMapLike({ "none": ModuleKind.None, "commonjs": ModuleKind.CommonJS, "amd": ModuleKind.AMD, @@ -109,7 +109,7 @@ namespace ts { }, { name: "newLine", - type: createMap({ + type: mapOfMapLike({ "crlf": NewLineKind.CarriageReturnLineFeed, "lf": NewLineKind.LineFeed }), @@ -258,7 +258,7 @@ namespace ts { { name: "target", shortName: "t", - type: createMap({ + type: mapOfMapLike({ "es3": ScriptTarget.ES3, "es5": ScriptTarget.ES5, "es6": ScriptTarget.ES6, @@ -292,7 +292,7 @@ namespace ts { }, { name: "moduleResolution", - type: createMap({ + type: mapOfMapLike({ "node": ModuleResolutionKind.NodeJs, "classic": ModuleResolutionKind.Classic, }), @@ -401,7 +401,7 @@ namespace ts { type: "list", element: { name: "lib", - type: createMap({ + type: mapOfMapLike({ // JavaScript only "es5": "lib.es5.d.ts", "es6": "lib.es2015.d.ts", @@ -473,8 +473,8 @@ namespace ts { /* @internal */ export interface OptionNameMap { - optionNameMap: Map; - shortOptionNames: Map; + optionNameMap: Map; + shortOptionNames: Map; } /* @internal */ @@ -493,12 +493,12 @@ namespace ts { return optionNameMapCache; } - const optionNameMap = createMap(); - const shortOptionNames = createMap(); + const optionNameMap = new StringMap(); + const shortOptionNames = new StringMap(); forEach(optionDeclarations, option => { - optionNameMap[option.name.toLowerCase()] = option; + optionNameMap.set(option.name.toLowerCase(), option); if (option.shortName) { - shortOptionNames[option.shortName] = option.name; + shortOptionNames.set(option.shortName, option.name); } }); @@ -509,18 +509,16 @@ namespace ts { /* @internal */ export function createCompilerDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType): Diagnostic { const namesOfType: string[] = []; - for (const key in opt.type) { - namesOfType.push(` '${key}'`); - } + forEachKeyInMap(opt.type, key => namesOfType.push(` '${key}'`)); return createCompilerDiagnostic(Diagnostics.Argument_for_0_option_must_be_Colon_1, `--${opt.name}`, namesOfType); } /* @internal */ export function parseCustomTypeOption(opt: CommandLineOptionOfCustomType, value: string, errors: Diagnostic[]) { const key = trimString((value || "")).toLowerCase(); - const map = opt.type; - if (key in map) { - return map[key]; + const customType = opt.type.get(key); + if (customType !== undefined) { + return customType; } else { errors.push(createCompilerDiagnosticForInvalidCustomType(opt)); @@ -573,13 +571,13 @@ namespace ts { s = s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1).toLowerCase(); // Try to translate short option names to their full equivalents. - if (s in shortOptionNames) { - s = shortOptionNames[s]; + const short = shortOptionNames.get(s); + if (short !== undefined) { + s = short; } - if (s in optionNameMap) { - const opt = optionNameMap[s]; - + const opt = optionNameMap.get(s); + if (opt !== undefined) { if (opt.isTSConfigOnly) { errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file, opt.name)); } @@ -696,7 +694,7 @@ namespace ts { * @param fileNames array of filenames to be generated into tsconfig.json */ /* @internal */ - export function generateTSConfig(options: CompilerOptions, fileNames: string[]): { compilerOptions: Map } { + export function generateTSConfig(options: CompilerOptions, fileNames: string[]): { compilerOptions: MapLike } { const compilerOptions = extend(options, defaultInitCompilerOptions); const configurations: any = { compilerOptions: serializeCompilerOptions(compilerOptions) @@ -708,7 +706,7 @@ namespace ts { return configurations; - function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption): Map | undefined { + function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption): Map | undefined { if (optionDefinition.type === "string" || optionDefinition.type === "number" || optionDefinition.type === "boolean") { // this is of a type CommandLineOptionOfPrimitiveType return undefined; @@ -721,18 +719,17 @@ namespace ts { } } - function getNameOfCompilerOptionValue(value: CompilerOptionsValue, customTypeMap: MapLike): string | undefined { + function getNameOfCompilerOptionValue(value: CompilerOptionsValue, customTypeMap: Map): string | undefined { // There is a typeMap associated with this command-line option so use it to map value back to its name - for (const key in customTypeMap) { - if (customTypeMap[key] === value) { + return findInMap(customTypeMap, (customValue, key) => { + if (customValue === value) { return key; } - } - return undefined; + }); } - function serializeCompilerOptions(options: CompilerOptions): Map { - const result = createMap(); + function serializeCompilerOptions(options: CompilerOptions): MapLike { + const result = new StringMap(); const optionsNameMap = getOptionNameMap().optionNameMap; for (const name in options) { @@ -748,13 +745,13 @@ namespace ts { break; default: const value = options[name]; - let optionDefinition = optionsNameMap[name.toLowerCase()]; + let optionDefinition = optionsNameMap.get(name.toLowerCase()); if (optionDefinition) { const customTypeMap = getCustomTypeMapOfCommandLineOption(optionDefinition); if (!customTypeMap) { // There is no map associated with this compiler option then use the value as-is // This is the case if the value is expect to be string, number, boolean or list of string - result[name] = value; + result.set(name, value); } else { if (optionDefinition.type === "list") { @@ -762,11 +759,11 @@ namespace ts { for (const element of value as (string | number)[]) { convertedValue.push(getNameOfCompilerOptionValue(element, customTypeMap)); } - result[name] = convertedValue; + result.set(name, convertedValue); } else { // There is a typeMap associated with this command-line option so use it to map value back to its name - result[name] = getNameOfCompilerOptionValue(value, customTypeMap); + result.set(name, getNameOfCompilerOptionValue(value, customTypeMap)); } } } @@ -774,7 +771,7 @@ namespace ts { } } } - return result; + return mapLikeOfMap(result); } } @@ -1001,8 +998,8 @@ namespace ts { const optionNameMap = arrayToMap(optionDeclarations, opt => opt.name); for (const id in jsonOptions) { - if (id in optionNameMap) { - const opt = optionNameMap[id]; + const opt = optionNameMap.get(id); + if (opt !== undefined) { defaultOptions[opt.name] = convertJsonOption(opt, jsonOptions[id], basePath, errors); } else { @@ -1038,8 +1035,9 @@ namespace ts { function convertJsonOptionOfCustomType(opt: CommandLineOptionOfCustomType, value: string, errors: Diagnostic[]) { const key = value.toLowerCase(); - if (key in opt.type) { - return opt.type[key]; + const val = opt.type.get(key); + if (val !== undefined) { + return val; } else { errors.push(createCompilerDiagnosticForInvalidCustomType(opt)); @@ -1148,12 +1146,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 = createMap(); + const literalFileMap = new StringMap(); // 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 = createMap(); + const wildcardFileMap = new StringMap(); if (include) { include = validateSpecs(include, errors, /*allowTrailingRecursion*/ false); @@ -1167,7 +1165,7 @@ namespace ts { // file map that marks whether it was a regular wildcard match (with a `*` or `?` token), // or a recursive directory. This information is used by filesystem watchers to monitor for // new entries in these paths. - const wildcardDirectories: Map = getWildcardDirectories(include, exclude, basePath, host.useCaseSensitiveFileNames); + const wildcardDirectories: Map = getWildcardDirectories(include, exclude, basePath, host.useCaseSensitiveFileNames); // Rather than requery this for each file and filespec, we query the supported extensions // once and store it on the expansion context. @@ -1178,7 +1176,7 @@ namespace ts { if (fileNames) { for (const fileName of fileNames) { const file = combinePaths(basePath, fileName); - literalFileMap[keyMapper(file)] = file; + literalFileMap.set(keyMapper(file), file); } } @@ -1201,18 +1199,17 @@ namespace ts { removeWildcardFilesWithLowerPriorityExtension(file, wildcardFileMap, supportedExtensions, keyMapper); const key = keyMapper(file); - if (!(key in literalFileMap) && !(key in wildcardFileMap)) { - wildcardFileMap[key] = file; + if (!literalFileMap.has(key)) { + setIfNotSet(wildcardFileMap, key, file); } } } - const literalFiles = reduceProperties(literalFileMap, addFileToOutput, []); - const wildcardFiles = reduceProperties(wildcardFileMap, addFileToOutput, []); - wildcardFiles.sort(host.useCaseSensitiveFileNames ? compareStrings : compareStringsCaseInsensitive); + const literalFiles = valuesOfMap(literalFileMap); + const wildcardFiles = valuesOfMap(wildcardFileMap).sort(host.useCaseSensitiveFileNames ? compareStrings : compareStringsCaseInsensitive); return { fileNames: literalFiles.concat(wildcardFiles), - wildcardDirectories + wildcardDirectories: mapLikeOfMap(wildcardDirectories) }; } @@ -1253,7 +1250,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 = createMap(); + const wildcardDirectories = new StringMap(); if (include !== undefined) { const recursiveKeys: string[] = []; for (const file of include) { @@ -1266,9 +1263,9 @@ namespace ts { if (match) { const key = useCaseSensitiveFileNames ? match[0] : match[0].toLowerCase(); const flags = watchRecursivePattern.test(name) ? WatchDirectoryFlags.Recursive : WatchDirectoryFlags.None; - const existingFlags = wildcardDirectories[key]; + const existingFlags = wildcardDirectories.get(key); if (existingFlags === undefined || existingFlags < flags) { - wildcardDirectories[key] = flags; + wildcardDirectories.set(key, flags); if (flags === WatchDirectoryFlags.Recursive) { recursiveKeys.push(key); } @@ -1277,13 +1274,13 @@ namespace ts { } // Remove any subpaths under an existing recursively watched directory. - for (const key in wildcardDirectories) { + forEachKeyInMap(wildcardDirectories, key => { for (const recursiveKey of recursiveKeys) { if (key !== recursiveKey && containsPath(recursiveKey, key, path, !useCaseSensitiveFileNames)) { - delete wildcardDirectories[key]; + wildcardDirectories.delete(key); } } - } + }); } return wildcardDirectories; @@ -1297,13 +1294,13 @@ namespace ts { * @param extensionPriority The priority of the extension. * @param context The expansion context. */ - function hasFileWithHigherPriorityExtension(file: string, literalFiles: Map, wildcardFiles: Map, extensions: string[], keyMapper: (value: string) => string) { + function hasFileWithHigherPriorityExtension(file: string, literalFiles: Map, wildcardFiles: Map, extensions: string[], keyMapper: (value: string) => string) { const extensionPriority = getExtensionPriority(file, extensions); const adjustedExtensionPriority = adjustExtensionPriority(extensionPriority); for (let i = ExtensionPriority.Highest; i < adjustedExtensionPriority; i++) { const higherPriorityExtension = extensions[i]; const higherPriorityPath = keyMapper(changeExtension(file, higherPriorityExtension)); - if (higherPriorityPath in literalFiles || higherPriorityPath in wildcardFiles) { + if (literalFiles.has(higherPriorityPath) || wildcardFiles.has(higherPriorityPath)) { return true; } } @@ -1319,27 +1316,16 @@ namespace ts { * @param extensionPriority The priority of the extension. * @param context The expansion context. */ - function removeWildcardFilesWithLowerPriorityExtension(file: string, wildcardFiles: Map, extensions: string[], keyMapper: (value: string) => string) { + function removeWildcardFilesWithLowerPriorityExtension(file: string, wildcardFiles: Map, extensions: string[], keyMapper: (value: string) => string) { const extensionPriority = getExtensionPriority(file, extensions); const nextExtensionPriority = getNextLowestExtensionPriority(extensionPriority); for (let i = nextExtensionPriority; i < extensions.length; i++) { const lowerPriorityExtension = extensions[i]; const lowerPriorityPath = keyMapper(changeExtension(file, lowerPriorityExtension)); - delete wildcardFiles[lowerPriorityPath]; + wildcardFiles.delete(lowerPriorityPath); } } - /** - * Adds a file to an array of files. - * - * @param output The output array. - * @param file The file path. - */ - function addFileToOutput(output: string[], file: string) { - output.push(file); - return output; - } - /** * Gets a case sensitive key. * diff --git a/src/compiler/core.ts b/src/compiler/core.ts index e63bcbf8474..4a9c8d7f771 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1,4 +1,5 @@ -/// +/// +/// /// /* @internal */ @@ -18,28 +19,8 @@ namespace ts { True = -1 } - const createObject = Object.create; - - 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 - // constantly changing shape. - map["__"] = undefined; - delete 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; - } - export function createFileMap(keyMapper?: (key: string) => string): FileMap { - let files = createMap(); + const files = new StringMap(); return { get, set, @@ -51,39 +32,33 @@ namespace ts { }; function forEachValueInMap(f: (key: Path, value: T) => void) { - for (const key in files) { - f(key, files[key]); - } + files.forEach((value, key) => f(key as Path, value)); } - function getKeys() { - const keys: Path[] = []; - for (const key in files) { - keys.push(key); - } - return keys; + function getKeys(): Path[] { + return keysOfMap(files) as Path[]; } // path should already be well-formed so it does not need to be normalized function get(path: Path): T { - return files[toKey(path)]; + return files.get(toKey(path)); } function set(path: Path, value: T) { - files[toKey(path)] = value; + files.set(toKey(path), value); } function contains(path: Path) { - return toKey(path) in files; + return files.has(toKey(path)); } function remove(path: Path) { const key = toKey(path); - delete files[key]; + files.delete(key); } function clear() { - files = createMap(); + files.clear(); } function toKey(path: Path): string { @@ -586,242 +561,6 @@ namespace ts { return initial; } - 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); - } - - /** - * 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. - * - * 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[] { - const keys: string[] = []; - for (const key in map) if (hasOwnProperty.call(map, key)) { - keys.push(key); - } - return keys; - } - - /** - * Enumerates the properties of a Map, invoking a callback and returning the first truthy result. - * - * @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; - } - - /** - * 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 - * - * @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]; - } - } - - export function assign, T2, T3>(t: T1, arg1: T2, arg2: T3): T1 & T2 & T3; - export function assign, T2>(t: T1, arg1: T2): T1 & T2; - export function assign>(t: T1, ...args: any[]): any; - export function assign>(t: T1, ...args: any[]) { - for (const arg of args) { - for (const p of getOwnKeys(arg)) { - t[p] = arg[p]; - } - } - return t; - } - - /** - * 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; - } - - /** - * Performs a shallow equality comparison of the contents of two map-likes. - * - * @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) 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; - } - return true; - } - - /** - * Creates a map from the elements of an array. - * - * @param array the array of input elements. - * @param makeKey a function that produces a key for a given element. - * - * This function makes no effort to avoid collisions; if any two elements produce - * 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; - 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; - } - - export function isEmpty(map: Map) { - for (const id in map) { - if (hasProperty(map, id)) { - return false; - } - } - return true; - } - - 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(first: T1 , second: T2): T1 & T2 { - const result: T1 & T2 = {}; - for (const id in second) if (hasOwnProperty.call(second, id)) { - (result as any)[id] = (second as any)[id]; - } - for (const id in first) if (hasOwnProperty.call(first, id)) { - (result as any)[id] = (first as any)[id]; - } - return result; - } - - /** - * Adds the value to an array of values associated with the key, and returns the array. - * Creates the array if it does not already exist. - */ - export function multiMapAdd(map: Map, key: string, value: V): V[] { - const values = map[key]; - if (values) { - values.push(value); - return values; - } - else { - return map[key] = [value]; - } - } - - /** - * Removes a value from an array of values associated with the key. - * Does not preserve the order of those values. - * Does nothing if `key` is not in `map`, or `value` is not in `map[key]`. - */ - export function multiMapRemove(map: Map, key: string, value: V): void { - const values = map[key]; - if (values) { - unorderedRemoveItem(values, value); - if (!values.length) { - delete map[key]; - } - } - } - /** * Tests whether a value is an array. */ @@ -912,10 +651,10 @@ namespace ts { return text.replace(/{(\d+)}/g, (match, index?) => args[+index + baseIndex]); } - export let localizedDiagnosticMessages: Map = undefined; + export let localizedDiagnosticMessages: Map = undefined; export function getLocaleSpecificMessage(message: DiagnosticMessage) { - return localizedDiagnosticMessages && localizedDiagnosticMessages[message.key] || message.message; + return localizedDiagnosticMessages && localizedDiagnosticMessages.get(message.key) || message.message; } export function createFileDiagnostic(file: SourceFile, start: number, length: number, message: DiagnosticMessage, ...args: any[]): Diagnostic; diff --git a/src/compiler/dataStructures.ts b/src/compiler/dataStructures.ts new file mode 100644 index 00000000000..57874c0f6c1 --- /dev/null +++ b/src/compiler/dataStructures.ts @@ -0,0 +1,624 @@ +// NumberMap, StringMap, and StringSet shims +/* @internal */ +namespace ts { + // The global Map object. This may not be available, so we must test for it. + declare const Map: NumberMapConstructor & StringMapConstructor | undefined; + const usingNativeMaps = typeof Map !== "undefined"; + // tslint:disable-next-line:no-in-operator + const fullyFeaturedMaps = usingNativeMaps && "keys" in Map.prototype && "values" in Map.prototype && "entries" in Map.prototype; + + /** Extra Map methods that may not be available, so we must provide fallbacks. */ + interface FullyFeaturedMap extends Map { + keys(): Iterator; + values(): Iterator; + entries(): Iterator<[K, V]>; + } + + /** Simplified ES6 Iterator interface. */ + interface Iterator { + next(): { value: T, done: false } | { value: never, done: true }; + } + + export interface NumberMapConstructor { + /** + * Creates a new Map with number keys. + * If `pairs` is provided, each [key, value] pair will be added to the map. + */ + new(pairs?: [K, V][]): Map; + } + + /** + * In runtimes without Maps, this is implemented using a sparse array. + * This is generic over the key type because it is usually an enum. + */ + export const NumberMap: NumberMapConstructor = usingNativeMaps ? Map : class ShimNumberMap implements Map { + private data: { [key: number]: V } = []; + + constructor(pairs?: [K, V][]) { + if (pairs) { + for (const [key, value] of pairs) { + this.data[key as number] = value; + } + } + } + + clear() { + this.data = []; + } + + delete(key: K) { + delete this.data[key as number]; + } + + get(key: K) { + return this.data[key as number]; + } + + has(key: K) { + // tslint:disable-next-line:no-in-operator + return (key as number) in this.data; + } + + set(key: K, value: V) { + this.data[key as number] = value; + } + + forEach(action: (value: V, key: K) => void) { + for (const key in this.data) { + action(this.data[key], key as any as K); + } + } + }; + + export interface StringMapConstructor { + new(): Map; + } + /** In runtimes without Maps, this is implemented using an object. */ + export const StringMap: StringMapConstructor = usingNativeMaps ? Map : class ShimStringMap implements Map { + private data = createDictionaryModeObject(); + + constructor() {} + + clear() { + this.data = createDictionaryModeObject(); + } + + delete(key: string) { + delete this.data[key]; + } + + get(key: string) { + return this.data[key]; + } + + has(key: string) { + // tslint:disable-next-line:no-in-operator + return key in this.data; + } + + set(key: string, value: T) { + this.data[key] = value; + } + + forEach(f: (value: T, key: string) => void) { + for (const key in this.data) { + f(this.data[key], key); + } + } + }; + + const createObject = Object.create; + function createDictionaryModeObject(): MapLike { + const 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 + // constantly changing shape. + map["__"] = undefined; + delete map["__"]; + + return map; + } + + /** Iterates over entries in the map, returning the first output of `getResult` that is not `undefined`. */ + export const findInMap: (map: Map, getResult: (value: V, key: K) => U | undefined) => U | undefined = fullyFeaturedMaps + ? (map: FullyFeaturedMap, f: (value: V, key: K) => U | undefined) => { + const iter = map.entries(); + while (true) { + const { value: pair, done } = iter.next(); + if (done) { + return undefined; + } + const [key, value] = pair; + const result = f(value, key); + if (result !== undefined) { + return result; + } + } + } + : (map: Map, f: (value: V, key: K) => U | undefined) => { + let result: U | undefined; + map.forEach((value, key) => { + if (result === undefined) + result = f(value, key); + }); + return result; + }; + + /** Whether `predicate` is true for at least one entry in the map. */ + export const someInMap: (map: Map, predicate: (value: V, key: K) => boolean) => boolean = fullyFeaturedMaps + ? (map: FullyFeaturedMap, predicate: (value: V, key: K) => boolean) => + someInIterator(map.entries(), ([key, value]) => predicate(value, key)) + : (map: Map, predicate: (value: V, key: K) => boolean) => { + let found = false; + map.forEach((value, key) => { + found = found || predicate(value, key); + }); + return found; + }; + + /** Whether `predicate` is true for at least one key in the map. */ + export const someKeyInMap: (map: Map, predicate: (key: K) => boolean) => boolean = fullyFeaturedMaps + ? (map: FullyFeaturedMap, predicate: (key: K) => boolean) => someInIterator(map.keys(), predicate) + : (map: Map, predicate: (key: K) => boolean) => + someInMap(map, (_value, key) => predicate(key)); + + /** Whether `predicate` is true for at least one value in the map. */ + export const someValueInMap: (map: Map, predicate: (value: T) => boolean) => boolean = fullyFeaturedMaps + ? (map: FullyFeaturedMap, predicate: (value: T) => boolean) => + someInIterator(map.values(), predicate) + : someInMap; + + function someInIterator(iterator: Iterator, predicate: (value: T) => boolean): boolean { + while (true) { + const { value, done } = iterator.next(); + if (done) { + return false; + } + if (predicate(value)) { + return true; + } + } + } + + /** + * Equivalent to the ES6 code: + * `for (const key of map.keys()) action(key);` + */ + export const forEachKeyInMap: (map: Map, action: (key: K) => void) => void = fullyFeaturedMaps + ? (map: FullyFeaturedMap, f: (key: K) => void) => { + const iter: Iterator = map.keys(); + while (true) { + const { value: key, done } = iter.next(); + if (done) { + return; + } + f(key); + } + } + : (map: Map, action: (key: K) => void) => { + map.forEach((_value, key) => action(key)); + }; + + /** Size of a map. */ + export const mapSize: (map: Map) => number = usingNativeMaps + ? map => (map as any).size + : map => { + let size = 0; + map.forEach(() => { size++; }); + return size; + }; + + /** Convert a Map to a MapLike. */ + export function mapLikeOfMap(map: Map): MapLike { + const obj = createDictionaryModeObject(); + map.forEach((value, key) => { + obj[key] = value; + }); + return obj; + } + + /** Create a map from a MapLike. This is useful for writing large maps as object literals. */ + export function mapOfMapLike(object: MapLike): Map { + const map = new StringMap(); + // 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 object) if (hasProperty(object, key)) { + map.set(key, object[key]); + } + return map; + } + + class ShimStringSet implements Set { + private data = createDictionaryModeObject(); + + constructor() {} + + add(value: string) { + this.data[value] = true; + } + + clear() { + this.data = createDictionaryModeObject(); + } + + delete(value: string) { + delete this.data[value]; + } + + forEach(action: (value: string) => void) { + for (const value in this.data) { + action(value); + } + } + + has(value: string) { + // tslint:disable-next-line:no-in-operator + return value in this.data; + } + + isEmpty() { + // tslint:disable-next-line:no-unused-variable + for (const _ in this.data) { + return false; + } + return true; + } + } + + declare const Set: { new(): Set } | undefined; + const usingNativeSets = typeof Set !== "undefined"; + + /** In runtimes without Sets, this is implemented using an object. */ + export const StringSet: { new(): Set } = usingNativeSets ? Set : ShimStringSet; + + /** False if there are any values in the set. */ + export const setIsEmpty: (set: Set) => boolean = usingNativeSets + ? set => (set as any).size === 0 + : (set: ShimStringSet) => set.isEmpty(); +} + +// Map utilities +namespace ts { + /** Create a map containing a single entry key -> value. */ + export function createMapWithEntry(key: string, value: T): Map { + const map = new StringMap(); + map.set(key, value); + return map; + } + + /** Set a value in a map, then return that value. */ + export function setAndReturn(map: Map, key: K, value: V): V { + map.set(key, value); + return value; + } + + /** False if there are any entries in the map. */ + export function mapIsEmpty(map: Map): boolean { + return !someKeyInMap(map, () => true); + } + + /** Create a new copy of a Map. */ + export function cloneMap(map: Map) { + const clone = new StringMap(); + copyMapEntriesFromTo(map, clone); + return clone; + } + + /** + * Performs a shallow copy of the properties from a source Map to a target Map + * + * @param source A map from which properties should be copied. + * @param target A map to which properties should be copied. + */ + export function copyMapEntriesFromTo(source: Map, target: Map): void { + source.forEach((value, key) => { + target.set(key, value); + }); + } + + /** Equivalent to `Array.from(map.keys())`. */ + export function keysOfMap(map: Map): K[] { + const keys: K[] = []; + forEachKeyInMap(map, key => { keys.push(key); }); + return keys; + } + + /** Equivalent to `Array.from(map.values())`. */ + export function valuesOfMap(map: Map): V[] { + const values: V[] = []; + map.forEach((value) => { values.push(value); }); + return values; + } + + /** Return a new map with each key transformed by `getNewKey`. */ + export function transformKeys(map: Map, getNewKey: (key: string) => string): Map { + const newMap = new StringMap(); + map.forEach((value, key) => { + newMap.set(getNewKey(key), value); + }); + return newMap; + } + + /** Replace each value with the result of calling `getNewValue`. */ + export function updateMapValues(map: Map, getNewValue: (value: V) => V): void { + map.forEach((value, key) => { + map.set(key, getNewValue(value)); + }); + } + + /** + * Change the value at `key` by applying the given function to it. + * If there is no value at `key` then `getNewValue` will be passed `undefined`. + */ + export function modifyValue(map: Map, key: K, getNewValue: (value: V) => V) { + map.set(key, getNewValue(map.get(key))); + } + + /** + * Get a value in the map, or if not already present, set and return it. + * Treats entries set to `undefined` as equivalent to not being set (saving a call to `has`). + */ + export function getOrUpdate(map: Map, key: K, getValue: (key: K) => V): V { + const value = map.get(key); + return value !== undefined ? value : setAndReturn(map, key, getValue(key)); + } + + /** Like `getOrUpdate`, but recognizes `undefined` as having been already set. */ + export function getOrUpdateAndAllowUndefined(map: Map, key: K, getValue: (key: K) => V): V { + return map.has(key) ? map.get(key) : setAndReturn(map, key, getValue(key)); + } + + /** + * Sets the the value if the key is not already in the map. + * Returns whether the value was set. + */ + export function setIfNotSet(map: Map, key: K, value: V): boolean { + const shouldSet = !map.has(key); + if (shouldSet) { + map.set(key, value); + } + return shouldSet; + } + + /** Deletes an entry from a map and returns it; or returns undefined if the key was not in the map. */ + export function tryDelete(map: Map, key: K): V | undefined { + const current = map.get(key); + if (current !== undefined) { + map.delete(key); + return current; + } + else { + return undefined; + } + } + + /** + * Creates a map from the elements of an array. + * + * @param array the array of input elements. + * @param makeKey a function that produces a key for a given element. + * + * This function makes no effort to avoid collisions; if any two elements produce + * 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; + 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 = new StringMap(); + for (const value of array) { + result.set(makeKey(value), makeValue ? makeValue(value) : value); + } + return result; + } + + /** + * Adds the value to an array of values associated with the key, and returns the array. + * Creates the array if it does not already exist. + */ + export function multiMapAdd(map: Map, key: K, value: V): V[] { + const values = map.get(key); + if (values) { + values.push(value); + return values; + } + else { + return setAndReturn(map, key, [value]); + } + } + + /** + * Removes a value from an array of values associated with the key. + * Does not preserve the order of those values. + * Does nothing if `key` is not in `map`, or `value` is not in `map[key]`. + */ + export function multiMapRemove(map: Map, key: K, value: V): void { + const values = map.get(key); + if (values) { + unorderedRemoveItem(values, value); + if (!values.length) { + map.delete(key); + } + } + } + + /** True if the maps have the same keys and values. */ + export function mapsAreEqual(left: Map, right: Map, valuesAreEqual?: (left: V, right: V) => boolean): boolean { + if (left === right) return true; + if (!left || !right) return false; + const someInLeftHasNoMatch = someInMap(left, (leftValue, leftKey) => { + if (!right.has(leftKey)) return true; + const rightValue = right.get(leftKey); + return !(valuesAreEqual ? valuesAreEqual(leftValue, rightValue) : leftValue === rightValue); + }); + if (someInLeftHasNoMatch) return false; + const someInRightHasNoMatch = someKeyInMap(right, rightKey => !left.has(rightKey)); + return !someInRightHasNoMatch; + } + + /** + * Creates a sorted array of keys. + * Sorts keys according to the iteration order they would have if they were in an object, instead of from a Map. + * This is so that tests run consistently whether or not we have a Map shim in place. + * The difference between Map iteration order and V8 object insertion order is that V8 moves natrual-number-like keys to the front. + */ + export function sortInV8ObjectInsertionOrder(values: T[], toKey: (t: T) => string): T[] { + const naturalNumberKeys: T[] = []; + const allOtherKeys: T[] = []; + for (const value of values) { + // "0" looks like a natural but "08" doesn't. + const looksLikeNatural = /^(0|([1-9]\d*))$/.test(toKey(value)); + (looksLikeNatural ? naturalNumberKeys : allOtherKeys).push(value); + } + function toInt(value: T): number { + return parseInt(toKey(value), 10); + } + naturalNumberKeys.sort((a, b) => toInt(a) - toInt(b)); + return naturalNumberKeys.concat(allOtherKeys); + } +} + +// Set utilities +/* @internal */ +namespace ts { + /** Union of the `getSet` of each element in the array. */ + export function setAggregate(array: T[], getSet: (t: T) => Set): Set { + const result = new StringSet(); + for (const value of array) { + copySetValuesFromTo(getSet(value), result); + } + return result; + } + + /** Adds all values in `source` to `target`. */ + function copySetValuesFromTo(source: Set, target: Set): void { + source.forEach(value => target.add(value)); + } + + /** Returns the values in `set` satisfying `predicate`. */ + export function filterSetToArray(set: Set, predicate: (value: T) => boolean): T[] { + const result: T[] = []; + set.forEach(value => { + if (predicate(value)) { + result.push(value); + } + }); + return result; + } +} + +// MapLike utilities +/* @internal */ +namespace ts { + const hasOwnProperty = Object.prototype.hasOwnProperty; + + 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; + } + + /** + * 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); + } + + /** + * 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. + * + * 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[] { + const keys: string[] = []; + for (const key in map) if (hasOwnProperty.call(map, key)) { + keys.push(key); + } + return keys; + } + + export function assign, T2, T3>(t: T1, arg1: T2, arg2: T3): T1 & T2 & T3; + export function assign, T2>(t: T1, arg1: T2): T1 & T2; + export function assign>(t: T1, ...args: any[]): any; + export function assign>(t: T1, ...args: any[]) { + for (const arg of args) { + for (const p of getOwnKeys(arg)) { + t[p] = arg[p]; + } + } + return t; + } + + /** + * Reduce the properties defined on a map-like (but not from its prototype chain). + * + * @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; + } + + /** + * Performs a shallow equality comparison of the contents of two map-likes. + * + * @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) 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; + } + return true; + } + + export function extend(first: T1 , second: T2): T1 & T2 { + const result: T1 & T2 = {}; + for (const id in second) if (hasOwnProperty.call(second, id)) { + (result as any)[id] = (second as any)[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/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index 4e19aafa484..8e55df7fcf3 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -59,7 +59,7 @@ namespace ts { let resultHasExternalModuleIndicator: boolean; let currentText: string; let currentLineMap: number[]; - let currentIdentifiers: Map; + let currentIdentifiers: Map; let isCurrentFileExternalModule: boolean; let reportedDeclarationError = false; let errorNameNode: DeclarationName; @@ -75,7 +75,7 @@ namespace ts { // and we could be collecting these paths from multiple files into single one with --out option let referencesOutput = ""; - let usedTypeDirectiveReferences: Map; + let usedTypeDirectiveReferences: Set; // Emit references corresponding to each file const emittedReferencedFiles: SourceFile[] = []; @@ -156,9 +156,9 @@ namespace ts { }); if (usedTypeDirectiveReferences) { - for (const directive in usedTypeDirectiveReferences) { + usedTypeDirectiveReferences.forEach(directive => { referencesOutput += `/// ${newLine}`; - } + }); } return { @@ -267,11 +267,11 @@ namespace ts { } if (!usedTypeDirectiveReferences) { - usedTypeDirectiveReferences = createMap(); + usedTypeDirectiveReferences = new StringSet(); } for (const directive of typeReferenceDirectives) { - if (!(directive in usedTypeDirectiveReferences)) { - usedTypeDirectiveReferences[directive] = directive; + if (!usedTypeDirectiveReferences.has(directive)) { + usedTypeDirectiveReferences.add(directive); } } } @@ -535,14 +535,14 @@ namespace ts { // do not need to keep track of created temp names. function getExportDefaultTempVariableName(): string { const baseName = "_default"; - if (!(baseName in currentIdentifiers)) { + if (!currentIdentifiers.has(baseName)) { return baseName; } let count = 0; while (true) { count++; const name = baseName + "_" + count; - if (!(name in currentIdentifiers)) { + if (!currentIdentifiers.has(name)) { return name; } } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index b3242e211de..4881bfcf3ef 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -219,11 +219,11 @@ const _super = (function (geti, seti) { let nodeIdToGeneratedName: string[]; let autoGeneratedIdToGeneratedName: string[]; - let generatedNameSet: Map; + let generatedNameSet: Set; let tempFlags: TempFlags; let currentSourceFile: SourceFile; let currentText: string; - let currentFileIdentifiers: Map; + let currentFileIdentifiers: Map; let extendsEmitted: boolean; let assignEmitted: boolean; let decorateEmitted: boolean; @@ -292,7 +292,7 @@ const _super = (function (geti, seti) { sourceMap.initialize(jsFilePath, sourceMapFilePath, sourceFiles, isBundledEmit); nodeIdToGeneratedName = []; autoGeneratedIdToGeneratedName = []; - generatedNameSet = createMap(); + generatedNameSet = new StringSet(); isOwnFileEmit = !isBundledEmit; // Emit helpers from all the files @@ -2621,15 +2621,16 @@ const _super = (function (geti, seti) { function isUniqueName(name: string): boolean { return !resolver.hasGlobalName(name) && - !hasProperty(currentFileIdentifiers, name) && - !hasProperty(generatedNameSet, name); + !currentFileIdentifiers.has(name) && + !generatedNameSet.has(name); } function isUniqueLocalName(name: string, container: Node): boolean { for (let node = container; isNodeDescendantOf(node, container); node = node.nextContainer) { - if (node.locals && hasProperty(node.locals, name)) { + if (node.locals) { + const local = node.locals.get(name); // 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)) { + if (local && local.flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias)) { return false; } } @@ -2678,7 +2679,8 @@ const _super = (function (geti, seti) { while (true) { const generatedName = baseName + i; if (isUniqueName(generatedName)) { - return generatedNameSet[generatedName] = generatedName; + generatedNameSet.add(generatedName); + return generatedName; } i++; } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index d873c3ef877..93e830fa1f3 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2659,9 +2659,9 @@ namespace ts { return destEmitNode; } - function mergeTokenSourceMapRanges(sourceRanges: Map, destRanges: Map) { - if (!destRanges) destRanges = createMap(); - copyProperties(sourceRanges, destRanges); + function mergeTokenSourceMapRanges(sourceRanges: Map, destRanges: Map): Map { + if (!destRanges) destRanges = new NumberMap(); + copyMapEntriesFromTo(sourceRanges, destRanges); return destRanges; } @@ -2753,8 +2753,8 @@ namespace ts { */ export function setTokenSourceMapRange(node: T, token: SyntaxKind, range: TextRange) { const emitNode = getOrCreateEmitNode(node); - const tokenSourceMapRanges = emitNode.tokenSourceMapRanges || (emitNode.tokenSourceMapRanges = createMap()); - tokenSourceMapRanges[token] = range; + const tokenSourceMapRanges = emitNode.tokenSourceMapRanges || (emitNode.tokenSourceMapRanges = new NumberMap()); + tokenSourceMapRanges.set(token, range); return node; } @@ -2795,7 +2795,7 @@ namespace ts { export function getTokenSourceMapRange(node: Node, token: SyntaxKind) { const emitNode = node.emitNode; const tokenSourceMapRanges = emitNode && emitNode.tokenSourceMapRanges; - return tokenSourceMapRanges && tokenSourceMapRanges[token]; + return tokenSourceMapRanges && tokenSourceMapRanges.get(token); } /** @@ -2880,10 +2880,8 @@ namespace ts { * Here we check if alternative name was provided for a given moduleName and return it if possible. */ function tryRenameExternalModule(moduleName: LiteralExpression, sourceFile: SourceFile) { - if (sourceFile.renamedDependencies && hasProperty(sourceFile.renamedDependencies, moduleName.text)) { - return createLiteral(sourceFile.renamedDependencies[moduleName.text]); - } - return undefined; + const rename = sourceFile.renamedDependencies && sourceFile.renamedDependencies.get(moduleName.text); + return rename && createLiteral(rename); } /** diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 51afa9b0a89..9ff3f789984 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -490,7 +490,7 @@ namespace ts { let currentToken: SyntaxKind; let sourceText: string; let nodeCount: number; - let identifiers: Map; + let identifiers: Map; let identifierCount: number; let parsingContext: ParsingContext; @@ -600,7 +600,7 @@ namespace ts { parseDiagnostics = []; parsingContext = 0; - identifiers = createMap(); + identifiers = new StringMap(); identifierCount = 0; nodeCount = 0; @@ -1101,7 +1101,7 @@ namespace ts { function internIdentifier(text: string): string { text = escapeIdentifier(text); - return identifiers[text] || (identifiers[text] = text); + return getOrUpdate(identifiers, text, 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 e8f064e81cd..b94386acc87 100644 --- a/src/compiler/performance.ts +++ b/src/compiler/performance.ts @@ -16,9 +16,9 @@ namespace ts.performance { let enabled = false; let profilerStart = 0; - let counts: Map; - let marks: Map; - let measures: Map; + let counts: Map; + let marks: Map; + let measures: Map; /** * Marks a performance event. @@ -27,8 +27,8 @@ namespace ts.performance { */ export function mark(markName: string) { if (enabled) { - marks[markName] = timestamp(); - counts[markName] = (counts[markName] || 0) + 1; + marks.set(markName, timestamp()); + counts.set(markName, (counts.get(markName) || 0) + 1); profilerEvent(markName); } } @@ -44,9 +44,9 @@ namespace ts.performance { */ 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); + const end = endMarkName && marks.get(endMarkName) || timestamp(); + const start = startMarkName && marks.get(startMarkName) || profilerStart; + measures.set(measureName, (measures.get(measureName) || 0) + (end - start)); } } @@ -56,7 +56,7 @@ namespace ts.performance { * @param markName The name of the mark. */ export function getCount(markName: string) { - return counts && counts[markName] || 0; + return counts && counts.get(markName) || 0; } /** @@ -65,7 +65,7 @@ namespace ts.performance { * @param measureName The name of the measure whose durations should be accumulated. */ export function getDuration(measureName: string) { - return measures && measures[measureName] || 0; + return measures && measures.get(measureName) || 0; } /** @@ -74,16 +74,14 @@ namespace ts.performance { * @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]); - } + measures.forEach((duration, measureName) => cb(measureName, duration)); } /** Enables (and resets) performance measurements for the compiler. */ export function enable() { - counts = createMap(); - marks = createMap(); - measures = createMap(); + counts = new StringMap(); + marks = new StringMap(); + measures = new StringMap(); enabled = true; profilerStart = timestamp(); } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index e300ac22fe5..cdadbe2a259 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -82,7 +82,7 @@ namespace ts { } export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost { - const existingDirectories = createMap(); + const existingDirectories = new StringSet(); 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. @@ -114,11 +114,11 @@ namespace ts { } function directoryExists(directoryPath: string): boolean { - if (directoryPath in existingDirectories) { + if (existingDirectories.has(directoryPath)) { return true; } if (sys.directoryExists(directoryPath)) { - existingDirectories[directoryPath] = true; + existingDirectories.add(directoryPath); return true; } return false; @@ -132,21 +132,21 @@ namespace ts { } } - let outputFingerprints: Map; + let outputFingerprints: Map; function writeFileIfUpdated(fileName: string, data: string, writeByteOrderMark: boolean): void { if (!outputFingerprints) { - outputFingerprints = createMap(); + outputFingerprints = new StringMap(); } const hash = sys.createHash(data); const mtimeBefore = sys.getModifiedTime(fileName); - if (mtimeBefore && fileName in outputFingerprints) { - const fingerprint = outputFingerprints[fileName]; + if (mtimeBefore) { + const fingerprint = outputFingerprints.get(fileName); // If output has not been changed, and the file has no external modification - if (fingerprint.byteOrderMark === writeByteOrderMark && + if (fingerprint !== undefined && fingerprint.byteOrderMark === writeByteOrderMark && fingerprint.hash === hash && fingerprint.mtime.getTime() === mtimeBefore.getTime()) { return; @@ -157,11 +157,11 @@ namespace ts { const mtimeAfter = sys.getModifiedTime(fileName); - outputFingerprints[fileName] = { + outputFingerprints.set(fileName, { hash, byteOrderMark: writeByteOrderMark, mtime: mtimeAfter - }; + }); } function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { @@ -279,11 +279,11 @@ namespace ts { return []; } const resolutions: T[] = []; - const cache = createMap(); + const cache = new StringMap(); for (const name of names) { - const result = name in cache - ? cache[name] - : cache[name] = loader(name, containingFile); + const result = cache.has(name) + ? cache.get(name) + : setAndReturn(cache, name, loader(name, containingFile)); resolutions.push(result); } return resolutions; @@ -295,9 +295,9 @@ namespace ts { let commonSourceDirectory: string; let diagnosticsProducingTypeChecker: TypeChecker; let noDiagnosticsTypeChecker: TypeChecker; - let classifiableNames: Map; + let classifiableNames: Set; - let resolvedTypeReferenceDirectives = createMap(); + let resolvedTypeReferenceDirectives = new StringMap(); 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. @@ -312,10 +312,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 = createMap(); + const modulesWithElidedImports = new StringMap(); // Track source files that are source files found by searching under node_modules, as these shouldn't be compiled. - const sourceFilesFoundSearchingNodeModules = createMap(); + const sourceFilesFoundSearchingNodeModules = new StringMap(); performance.mark("beforeProgram"); @@ -440,15 +440,11 @@ namespace ts { return commonSourceDirectory; } - function getClassifiableNames() { + function getClassifiableNames(): Set { if (!classifiableNames) { // Initialize a checker so that all our files are bound. getTypeChecker(); - classifiableNames = createMap(); - - for (const sourceFile of files) { - copyProperties(sourceFile.classifiableNames, classifiableNames); - } + classifiableNames = setAggregate(files, sourceFile => sourceFile.classifiableNames); } return classifiableNames; @@ -598,7 +594,7 @@ namespace ts { getSourceFile: program.getSourceFile, getSourceFileByPath: program.getSourceFileByPath, getSourceFiles: program.getSourceFiles, - isSourceFileFromExternalLibrary: (file: SourceFile) => !!sourceFilesFoundSearchingNodeModules[file.path], + isSourceFileFromExternalLibrary: (file: SourceFile) => !!sourceFilesFoundSearchingNodeModules.get(file.path), writeFile: writeFileCallback || ( (fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)), isEmitBlocked, @@ -1142,8 +1138,8 @@ namespace ts { // Get source file from normalized fileName function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, isReference: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number): SourceFile { - if (filesByName.contains(path)) { - const file = filesByName.get(path); + let file = filesByName.get(path); + if (file !== undefined) { // try to check if we've already seen this file but with a different casing in path // NOTE: this only makes sense for case-insensitive file systems if (file && options.forceConsistentCasingInFileNames && getNormalizedAbsolutePath(file.fileName, currentDirectory) !== getNormalizedAbsolutePath(fileName, currentDirectory)) { @@ -1152,20 +1148,20 @@ 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 && sourceFilesFoundSearchingNodeModules[file.path] && currentNodeModulesDepth == 0) { - sourceFilesFoundSearchingNodeModules[file.path] = false; + if (file && sourceFilesFoundSearchingNodeModules.get(file.path) && currentNodeModulesDepth == 0) { + sourceFilesFoundSearchingNodeModules.set(file.path, false); if (!options.noResolve) { processReferencedFiles(file, getDirectoryPath(fileName), isDefaultLib); processTypeReferenceDirectives(file); } - modulesWithElidedImports[file.path] = false; + modulesWithElidedImports.set(file.path, false); processImportedModules(file, getDirectoryPath(fileName)); } // See if we need to reprocess the imports due to prior skipped imports - else if (file && modulesWithElidedImports[file.path]) { + else if (file && modulesWithElidedImports.get(file.path)) { if (currentNodeModulesDepth < maxNodeModulesJsDepth) { - modulesWithElidedImports[file.path] = false; + modulesWithElidedImports.set(file.path, false); processImportedModules(file, getDirectoryPath(fileName)); } } @@ -1174,7 +1170,7 @@ namespace ts { } // We haven't looked for this file, do so now and cache result - const file = host.getSourceFile(fileName, options.target, hostErrorMessage => { + file = host.getSourceFile(fileName, options.target, hostErrorMessage => { if (refFile !== undefined && refPos !== undefined && refEnd !== undefined) { fileProcessingDiagnostics.add(createFileDiagnostic(refFile, refPos, refEnd - refPos, Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage)); @@ -1186,7 +1182,7 @@ namespace ts { filesByName.set(path, file); if (file) { - sourceFilesFoundSearchingNodeModules[path] = (currentNodeModulesDepth > 0); + sourceFilesFoundSearchingNodeModules.set(path, currentNodeModulesDepth > 0); file.path = path; if (host.useCaseSensitiveFileNames()) { @@ -1248,7 +1244,7 @@ namespace ts { refFile?: SourceFile, refPos?: number, refEnd?: number): void { // If we already found this library as a primary reference - nothing to do - const previousResolution = resolvedTypeReferenceDirectives[typeReferenceDirective]; + const previousResolution = resolvedTypeReferenceDirectives.get(typeReferenceDirective); if (previousResolution && previousResolution.primary) { return; } @@ -1285,7 +1281,7 @@ namespace ts { } if (saveResolution) { - resolvedTypeReferenceDirectives[typeReferenceDirective] = resolvedTypeReferenceDirective; + resolvedTypeReferenceDirectives.set(typeReferenceDirective, resolvedTypeReferenceDirective); } } @@ -1305,7 +1301,7 @@ namespace ts { function processImportedModules(file: SourceFile, basePath: string) { collectExternalModuleReferences(file); if (file.imports.length || file.moduleAugmentations.length) { - file.resolvedModules = createMap(); + file.resolvedModules = new StringMap(); 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++) { @@ -1329,7 +1325,7 @@ namespace ts { const shouldAddFile = resolution && !options.noResolve && i < file.imports.length && !elideImport; if (elideImport) { - modulesWithElidedImports[file.path] = true; + modulesWithElidedImports.set(file.path, true); } else if (shouldAddFile) { findSourceFile(resolution.resolvedFileName, diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index e7b3872f2e3..ccf3fa38950 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -56,7 +56,7 @@ namespace ts { tryScan(callback: () => T): T; } - const textToToken = createMap({ + const textToToken = mapOfMapLike({ "abstract": SyntaxKind.AbstractKeyword, "any": SyntaxKind.AnyKeyword, "as": SyntaxKind.AsKeyword, @@ -272,11 +272,11 @@ namespace ts { lookupInUnicodeMap(code, unicodeES3IdentifierPart); } - function makeReverseMap(source: Map): string[] { + function makeReverseMap(source: Map): string[] { const result: string[] = []; - for (const name in source) { - result[source[name]] = name; - } + source.forEach((num, name) => { + result[num] = name; + }); return result; } @@ -288,7 +288,7 @@ namespace ts { /* @internal */ export function stringToToken(s: string): SyntaxKind { - return textToToken[s]; + return textToToken.get(s); } /* @internal */ @@ -362,8 +362,6 @@ namespace ts { return computeLineAndCharacterOfPosition(getLineStarts(sourceFile), position); } - const hasOwnProperty = Object.prototype.hasOwnProperty; - export function isWhiteSpace(ch: number): boolean { return isWhiteSpaceSingleLine(ch) || isLineBreak(ch); } @@ -1182,8 +1180,11 @@ namespace ts { const len = tokenValue.length; if (len >= 2 && len <= 11) { const ch = tokenValue.charCodeAt(0); - if (ch >= CharacterCodes.a && ch <= CharacterCodes.z && hasOwnProperty.call(textToToken, tokenValue)) { - return token = textToToken[tokenValue]; + if (ch >= CharacterCodes.a && ch <= CharacterCodes.z) { + token = textToToken.get(tokenValue); + if (token !== undefined) { + return token; + } } } return token = SyntaxKind.Identifier; diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 46db5188e33..fcf85aa86a6 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -362,7 +362,7 @@ namespace ts { const emitNode = node && node.emitNode; const emitFlags = emitNode && emitNode.flags; - const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges[token]; + const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges.get(token); tokenPos = skipTrivia(currentSourceText, range ? range.pos : tokenPos); if ((emitFlags & EmitFlags.NoTokenLeadingSourceMaps) === 0 && tokenPos >= 0) { diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 7cf7d75bc02..784c40f4d22 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -239,25 +239,25 @@ namespace ts { const useNonPollingWatchers = process.env["TSC_NONPOLLING_WATCHER"]; function createWatchedFileSet() { - const dirWatchers = createMap(); + const dirWatchers = new StringMap(); // One file can have multiple watchers - const fileWatcherCallbacks = createMap(); + const fileWatcherCallbacks = new StringMap(); return { addFile, removeFile }; function reduceDirWatcherRefCountForFile(fileName: string) { const dirName = getDirectoryPath(fileName); - const watcher = dirWatchers[dirName]; + const watcher = dirWatchers.get(dirName); if (watcher) { watcher.referenceCount -= 1; if (watcher.referenceCount <= 0) { watcher.close(); - delete dirWatchers[dirName]; + dirWatchers.delete(dirName); } } } function addDirWatcher(dirPath: string): void { - let watcher = dirWatchers[dirPath]; + let watcher = dirWatchers.get(dirPath); if (watcher) { watcher.referenceCount += 1; return; @@ -268,7 +268,7 @@ namespace ts { (eventName: string, relativeFileName: string) => fileEventHandler(eventName, relativeFileName, dirPath) ); watcher.referenceCount = 1; - dirWatchers[dirPath] = watcher; + dirWatchers.set(dirPath, watcher); return; } @@ -298,9 +298,12 @@ namespace ts { ? undefined : ts.getNormalizedAbsolutePath(relativeFileName, baseDirPath); // Some applications save a working file via rename operations - if ((eventName === "change" || eventName === "rename") && fileWatcherCallbacks[fileName]) { - for (const fileCallback of fileWatcherCallbacks[fileName]) { - fileCallback(fileName); + if ((eventName === "change" || eventName === "rename")) { + const callbacks = fileWatcherCallbacks.get(fileName); + if (callbacks) { + for (const fileCallback of fileWatcherCallbacks.get(fileName)) { + fileCallback(fileName); + } } } } diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 92407af2bb9..e341fd6f69f 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -10,14 +10,14 @@ /* @internal */ namespace ts { - const moduleTransformerMap = createMap({ - [ModuleKind.ES6]: transformES6Module, - [ModuleKind.System]: transformSystemModule, - [ModuleKind.AMD]: transformModule, - [ModuleKind.CommonJS]: transformModule, - [ModuleKind.UMD]: transformModule, - [ModuleKind.None]: transformModule, - }); + const moduleTransformerMap = new NumberMap([ + [ModuleKind.ES6, transformES6Module], + [ModuleKind.System, transformSystemModule], + [ModuleKind.AMD, transformModule], + [ModuleKind.CommonJS, transformModule], + [ModuleKind.UMD, transformModule], + [ModuleKind.None, transformModule], + ]); const enum SyntaxKindFeatureFlags { Substitution = 1 << 0, @@ -109,7 +109,7 @@ namespace ts { const transformers: Transformer[] = []; transformers.push(transformTypeScript); - transformers.push(moduleTransformerMap[moduleKind] || moduleTransformerMap[ModuleKind.None]); + transformers.push(moduleTransformerMap.get(moduleKind) || moduleTransformerMap.get(ModuleKind.None)); if (jsx === JsxEmit.React) { transformers.push(transformJsx); @@ -339,4 +339,4 @@ namespace ts { return statements; } } -} \ No newline at end of file +} diff --git a/src/compiler/transformers/es6.ts b/src/compiler/transformers/es6.ts index 7fb0594a6cc..12ce9d53fd0 100644 --- a/src/compiler/transformers/es6.ts +++ b/src/compiler/transformers/es6.ts @@ -70,15 +70,15 @@ namespace ts { * set of labels that occurred inside the converted loop * used to determine if labeled jump can be emitted as is or it should be dispatched to calling code */ - labels?: Map; + labels?: Map; /* * collection of labeled jumps that transfer control outside the converted loop. * maps store association 'label -> labelMarker' where * - label - value of label as it appear in code * - label marker - return value that should be interpreted by calling code as 'jump to