diff --git a/Jakefile.js b/Jakefile.js index 8ce70eb353f9e..8425751c4d41c 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -57,6 +57,7 @@ function measure(marker) { } var compilerSources = [ + "collections.ts", "core.ts", "performance.ts", "sys.ts", @@ -93,6 +94,7 @@ var compilerSources = [ }); var servicesSources = [ + "collections.ts", "core.ts", "performance.ts", "sys.ts", diff --git a/scripts/processDiagnosticMessages.ts b/scripts/processDiagnosticMessages.ts index e5eaa46c8e5b9..a7e2f24fdb885 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 = ts.createMap(); 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 9c2c749e112a6..9bf8d4846ce12 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -133,7 +133,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 }; @@ -147,7 +147,7 @@ namespace ts { options = opts; languageVersion = getEmitScriptTarget(options); inStrictMode = bindInStrictMode(file, opts); - classifiableNames = createMap(); + classifiableNames = createSet(); symbolCount = 0; skipTransformFlagAggregation = isDeclarationFile(file); @@ -207,11 +207,11 @@ namespace ts { symbol.declarations.push(node); if (symbolFlags & SymbolFlags.HasExports && !symbol.exports) { - symbol.exports = createMap(); + symbol.exports = createMap(); } if (symbolFlags & SymbolFlags.HasMembers && !symbol.members) { - symbol.members = createMap(); + symbol.members = createMap(); } if (symbolFlags & SymbolFlags.Value) { @@ -349,17 +349,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) { @@ -484,7 +484,7 @@ namespace ts { if (containerFlags & ContainerFlags.IsContainer) { container = blockScopeContainer = node; if (containerFlags & ContainerFlags.HasLocals) { - container.locals = createMap(); + container.locals = createMap(); } addToContainerChain(container); } @@ -1525,8 +1525,7 @@ namespace ts { const typeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, "__type"); addDeclarationToSymbol(typeLiteralSymbol, node, SymbolFlags.TypeLiteral); - typeLiteralSymbol.members = createMap(); - typeLiteralSymbol.members[symbol.name] = symbol; + typeLiteralSymbol.members = createMap([[symbol.name, symbol]]); } function bindObjectLiteralExpression(node: ObjectLiteralExpression) { @@ -1536,7 +1535,7 @@ namespace ts { } if (inStrictMode) { - const seen = createMap(); + const seen = createMap(); for (const prop of node.properties) { if (prop.name.kind !== SyntaxKind.Identifier) { @@ -1557,9 +1556,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; } @@ -1592,7 +1591,7 @@ namespace ts { // fall through. default: if (!blockScopeContainer.locals) { - blockScopeContainer.locals = createMap(); + blockScopeContainer.locals = createMap(); addToContainerChain(blockScopeContainer); } declareSymbol(blockScopeContainer.locals, undefined, node, symbolFlags, symbolExcludes); @@ -2072,7 +2071,7 @@ namespace ts { } } - file.symbol.globalExports = file.symbol.globalExports || createMap(); + file.symbol.globalExports = file.symbol.globalExports || createMap(); declareSymbol(file.symbol.globalExports, file.symbol, node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); } @@ -2119,7 +2118,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 || createMap(); // It's acceptable for multiple 'this' assignments of the same identifier to occur declareSymbol(container.symbol.members, container.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); } @@ -2151,14 +2150,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 = createMap(); } // Declare the method/property @@ -2191,7 +2190,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); } } @@ -2207,14 +2206,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 4f8f9b3f68c7c..281190d325653 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 = createMap(); 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 = createMap(); + const intersectionTypes = createMap(); + const stringLiteralTypes = createMap(); + const numericLiteralTypes = createMap(); const evolvingArrayTypes: EvolvingArrayType[] = []; const unknownSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "unknown"); @@ -139,7 +139,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 = createMap(); const anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); // The anyFunctionType contains the anyFunctionType by definition. The flag is further propagated @@ -155,7 +155,7 @@ namespace ts { const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true); - const globals = createMap(); + const globals = createMap(); /** * List of every ambient module with a "*" wildcard. * Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches. @@ -221,7 +221,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[][] = []; @@ -295,7 +295,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, @@ -305,7 +305,7 @@ namespace ts { "function": TypeFacts.TypeofEQFunction }); - const typeofNEFacts = createMap({ + const typeofNEFacts = mapOfMapLike({ "string": TypeFacts.TypeofNEString, "number": TypeFacts.TypeofNENumber, "boolean": TypeFacts.TypeofNEBoolean, @@ -315,7 +315,7 @@ namespace ts { "function": TypeFacts.TypeofNEFunction }); - const typeofTypesByName = createMap({ + const typeofTypesByName = mapOfMapLike({ "string": stringType, "number": numberType, "boolean": booleanType, @@ -325,7 +325,7 @@ namespace ts { let jsxElementType: Type; /** Things we lazy load from the JSX namespace */ - const jsxTypes = createMap(); + const jsxTypes = createMap(); const JsxNames = { JSX: "JSX", IntrinsicElements: "IntrinsicElements", @@ -336,11 +336,11 @@ namespace ts { IntrinsicClassAttributes: "IntrinsicClassAttributes" }; - const subtypeRelation = createMap(); - const assignableRelation = createMap(); - const comparableRelation = createMap(); - const identityRelation = createMap(); - const enumRelation = createMap(); + const subtypeRelation = createMap(); + const assignableRelation = createMap(); + const comparableRelation = createMap(); + const identityRelation = createMap(); + const enumRelation = createMap(); // This is for caching the result of getSymbolDisplayBuilder. Do not access directly. let _displayBuilder: SymbolDisplayBuilder; @@ -354,8 +354,7 @@ namespace ts { ResolvedReturnType } - const builtinGlobals = createMap(); - builtinGlobals[undefinedSymbol.name] = undefinedSymbol; + const builtinGlobals = createMap([[undefinedSymbol.name, undefinedSymbol]]); initializeTypeChecker(); @@ -438,11 +437,11 @@ namespace ts { target.declarations.push(node); }); if (source.members) { - if (!target.members) target.members = createMap(); + if (!target.members) target.members = createMap(); mergeSymbolTable(target.members, source.members); } if (source.exports) { - if (!target.exports) target.exports = createMap(); + if (!target.exports) target.exports = createMap(); mergeSymbolTable(target.exports, source.exports); } recordMergedSymbol(target, source); @@ -460,18 +459,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 { @@ -512,15 +511,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)); @@ -548,7 +548,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) { @@ -733,7 +733,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; @@ -752,9 +752,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; } } @@ -1069,11 +1070,16 @@ namespace ts { const moduleSymbol = resolveExternalModuleName(node, (node.parent).moduleSpecifier); if (moduleSymbol) { - const exportDefaultSymbol = isUntypedModuleSymbol(moduleSymbol) ? - moduleSymbol : - moduleSymbol.exports["export="] ? - getPropertyOfType(getTypeOfSymbol(moduleSymbol.exports["export="]), "default") : - resolveSymbol(moduleSymbol.exports["default"]); + let exportDefaultSymbol: Symbol; + if (isUntypedModuleSymbol(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)); @@ -1123,7 +1129,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); } @@ -1151,7 +1157,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 { @@ -1412,9 +1418,9 @@ namespace ts { // This provides a name to the module. See the test tests/cases/fourslash/untypedModuleImport.ts const newSymbol = createSymbol(SymbolFlags.ValueModule, quotedName); // Module symbols are expected to have 'exports', although since this is an untyped module it can be empty. - newSymbol.exports = createMap(); + newSymbol.exports = createMap(); // Cache it so subsequent accesses will return the same module. - globals[quotedName] = newSymbol; + globals.set(quotedName, newSymbol); return newSymbol; } @@ -1440,7 +1446,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 =' @@ -1456,7 +1462,7 @@ namespace ts { } function hasExportAssignmentSymbol(moduleSymbol: Symbol): boolean { - return moduleSymbol.exports["export="] !== undefined; + return moduleSymbol.exports.get("export=") !== undefined; } function getExportsOfModuleAsArray(moduleSymbol: Symbol): Symbol[] { @@ -1481,25 +1487,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 { @@ -1519,10 +1529,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 = createMap(); + const lookupTable = createMap(); for (const node of exportStars.declarations) { const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier); const exportedSymbols = visit(resolvedModule); @@ -1533,21 +1543,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; @@ -1642,15 +1651,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; } @@ -1723,12 +1731,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)) { @@ -1763,7 +1771,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; @@ -2500,7 +2508,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); @@ -3229,7 +3237,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 = createMap(); let hasComputedProperties = false; forEach(pattern.elements, e => { const name = e.propertyName || e.name; @@ -3244,7 +3252,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) { @@ -3834,8 +3842,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 = createMap([[getTypeListId(type.typeParameters), type]]); (type).target = type; (type).typeArguments = type.typeParameters; type.thisType = createType(TypeFlags.TypeParameter); @@ -3877,8 +3884,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 = createMap([[getTypeListId(links.typeParameters), type]]); } } else { @@ -3898,7 +3904,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) { @@ -3929,7 +3935,7 @@ namespace ts { enumType.symbol = symbol; if (enumHasLiteralMembers(symbol)) { const memberTypeList: Type[] = []; - const memberTypes = createMap(); + const memberTypes: { [enumMemberValue: number]: EnumLiteralType } = []; for (const declaration of enumType.symbol.declarations) { if (declaration.kind === SyntaxKind.EnumDeclaration) { computeEnumMemberValues(declaration); @@ -3937,8 +3943,7 @@ namespace ts { const memberSymbol = getSymbolOfNode(member); const value = getEnumMemberValue(member); if (!memberTypes[value]) { - const memberType = memberTypes[value] = createEnumLiteralType(memberSymbol, enumType, "" + value); - memberTypeList.push(memberType); + memberTypeList.push(memberTypes[value] = createEnumLiteralType(memberSymbol, enumType, "" + value)); } } } @@ -3947,7 +3952,7 @@ namespace ts { if (memberTypeList.length > 1) { enumType.flags |= TypeFlags.Union; (enumType).types = memberTypeList; - unionTypes[getTypeListId(memberTypeList)] = enumType; + unionTypes.set(getTypeListId(memberTypeList), enumType); } } } @@ -4089,27 +4094,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); } } } @@ -4118,8 +4115,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); } @@ -4359,8 +4356,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); setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); @@ -4374,7 +4371,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); } @@ -4432,7 +4429,7 @@ namespace ts { function getPropertyOfObjectType(type: Type, name: string): Symbol { if (type.flags & TypeFlags.Object) { const resolved = resolveStructuredTypeMembers(type); - const symbol = resolved.members[name]; + const symbol = resolved.members.get(name); if (symbol && symbolIsValue(symbol)) { return symbol; } @@ -4453,13 +4450,12 @@ namespace ts { const props = type.resolvedProperties; if (props) { const result: Symbol[] = []; - for (const key in props) { - const prop = props[key]; + props.forEach(prop => { // We need to filter out partial properties in union types if (!(prop.flags & SymbolFlags.SyntheticProperty && (prop).isPartial)) { result.push(prop); } - } + }); return result; } return emptyArray; @@ -4576,12 +4572,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 = createMap()); + let property = properties.get(name); if (!property) { property = createUnionOrIntersectionProperty(type, name); if (property) { - properties[name] = property; + properties.set(name, property); } } return property; @@ -4605,7 +4601,7 @@ namespace ts { type = getApparentType(type); if (type.flags & TypeFlags.Object) { const resolved = resolveStructuredTypeMembers(type); - const symbol = resolved.members[name]; + const symbol = resolved.members.get(name); if (symbol && symbolIsValue(symbol)) { return symbol; } @@ -4704,11 +4700,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; } @@ -5000,7 +4996,7 @@ namespace ts { } function getIndexSymbol(symbol: Symbol): Symbol { - return symbol.members["__index"]; + return symbol.members.get("__index"); } function getIndexDeclarationOfSymbol(symbol: Symbol, kind: IndexKind): SignatureDeclaration { @@ -5114,9 +5110,9 @@ 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) { - type = target.instantiations[id] = createObjectType(ObjectFlags.Reference, target.symbol); + type = setAndReturn(target.instantiations, id, createObjectType(ObjectFlags.Reference, target.symbol)); type.flags |= typeArguments ? getPropagatingFlagsOfTypes(typeArguments, /*excludeKinds*/ 0) : 0; type.target = target; type.typeArguments = typeArguments; @@ -5172,7 +5168,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)); @@ -5397,8 +5393,7 @@ namespace ts { type.typeParameters = typeParameters; type.outerTypeParameters = undefined; type.localTypeParameters = typeParameters; - type.instantiations = createMap(); - type.instantiations[getTypeListId(type.typeParameters)] = type; + type.instantiations = createMap([[getTypeListId(type.typeParameters), type]]); type.target = type; type.typeArguments = type.typeParameters; type.thisType = createType(TypeFlags.TypeParameter); @@ -5602,10 +5597,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] = createType(TypeFlags.Union | propagatedFlags); + type = setAndReturn(unionTypes, id, createType(TypeFlags.Union | propagatedFlags)); type.types = types; type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = aliasTypeArguments; @@ -5673,10 +5668,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] = createType(TypeFlags.Intersection | propagatedFlags); + type = setAndReturn(intersectionTypes, id, createType(TypeFlags.Intersection | propagatedFlags)); type.types = typeSet; type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = aliasTypeArguments; @@ -5728,7 +5723,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 { @@ -6424,13 +6419,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))) { @@ -6441,14 +6437,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; @@ -6476,7 +6472,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; } @@ -6488,7 +6484,7 @@ namespace ts { } if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { 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; } @@ -6512,7 +6508,7 @@ namespace ts { function checkTypeRelatedTo( source: Type, target: Type, - relation: Map, + relation: Map, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: DiagnosticMessageChain): boolean { @@ -6520,7 +6516,7 @@ namespace ts { let errorInfo: DiagnosticMessageChain; let sourceStack: Type[]; let targetStack: Type[]; - let maybeStack: Map[]; + let maybeStack: Map[]; let expandingFlags: number; let depth = 0; let overflow = false; @@ -6879,12 +6875,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; @@ -6893,7 +6889,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; } } @@ -6910,8 +6906,7 @@ namespace ts { } sourceStack[depth] = source; targetStack[depth] = target; - maybeStack[depth] = createMap(); - maybeStack[depth][id] = RelationComparisonResult.Succeeded; + maybeStack[depth] = createMap([[id, RelationComparisonResult.Succeeded]]); depth++; const saveExpandingFlags = expandingFlags; if (!(expandingFlags & 1) && isDeeplyNestedGeneric(source, sourceStack, depth)) expandingFlags |= 1; @@ -6941,12 +6936,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; } @@ -7135,14 +7130,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; } @@ -7581,11 +7591,11 @@ namespace ts { } function transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) { - const members = createMap(); + const members = createMap(); 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; } @@ -7804,7 +7814,7 @@ namespace ts { let targetStack: Type[]; let depth = 0; let inferiority = 0; - const visited = createMap(); + const visited = createSet(); inferFromTypes(originalSource, originalTarget); function isInProcess(source: Type, target: Type) { @@ -7940,10 +7950,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 = []; @@ -8294,7 +8304,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 { @@ -8884,12 +8894,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] = createMap()); 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. @@ -8923,8 +8934,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); @@ -8948,7 +8960,7 @@ namespace ts { if (isIncomplete(firstAntecedentType)) { return createFlowType(result, /*incomplete*/ true); } - return cache[key] = result; + return setAndReturn(cache, key, result); } function isMatchingReferenceDiscriminant(expr: Expression) { @@ -9071,14 +9083,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); } @@ -10582,7 +10594,7 @@ namespace ts { // Grammar checking checkGrammarObjectLiteralExpression(node, inDestructuringPattern); - const propertiesTable = createMap(); + const propertiesTable = createMap(); const propertiesArray: Symbol[] = []; const contextualType = getApparentTypeOfContextualType(node); const contextualTypeHasPattern = contextualType && contextualType.pattern && @@ -10664,7 +10676,7 @@ namespace ts { } } else { - propertiesTable[member.name] = member; + propertiesTable.set(member.name, member); } propertiesArray.push(member); } @@ -10673,12 +10685,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); } } @@ -10755,7 +10767,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 @@ -10794,34 +10806,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; } /** @@ -11132,7 +11145,7 @@ namespace ts { const targetAttributesType = getJsxElementAttributesType(node); - const nameTable = createMap(); + const nameTable = createSet(); // 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 @@ -11156,7 +11169,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)); } @@ -11886,7 +11899,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 @@ -12420,7 +12433,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; @@ -14609,8 +14622,8 @@ namespace ts { Property = Getter | Setter } - const instanceNames = createMap(); - const staticNames = createMap(); + const instanceNames = createMap(); + const staticNames = createMap(); for (const member of node.members) { if (member.kind === SyntaxKind.Constructor) { for (const param of (member as ConstructorDeclaration).parameters) { @@ -14642,24 +14655,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 = createSet(); for (const member of node.members) { if (member.kind == SyntaxKind.PropertySignature) { let memberName: string; @@ -14673,12 +14686,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); } } } @@ -15883,8 +15896,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 && getRootDeclaration(local.valueDeclaration).kind === SyntaxKind.Parameter) { const parameter = getRootDeclaration(local.valueDeclaration); @@ -15899,7 +15911,7 @@ namespace ts { forEach(local.declarations, d => errorUnusedLocal(d.name || d, local.name)); } } - } + }); } } @@ -15965,8 +15977,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)) { @@ -15974,7 +15985,7 @@ namespace ts { } } } - } + }); } } @@ -16991,12 +17002,12 @@ namespace ts { else { const blockLocals = catchClause.block.locals; if (blockLocals) { - for (const caughtName in catchClause.locals) { - const blockLocal = blockLocals[caughtName]; + forEachKeyInMap(catchClause.locals, caughtName => { + const blockLocal = blockLocals.get(caughtName); if (blockLocal && (blockLocal.flags & SymbolFlags.BlockScopedVariable) !== 0) { grammarErrorOnNode(blockLocal.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, caughtName); } - } + }); } } } @@ -17415,16 +17426,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; @@ -18166,19 +18176,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)) { @@ -18187,21 +18192,20 @@ namespace ts { } // Checks for export * conflicts const exports = getExportsOfModule(moduleSymbol); - for (const id in exports) { + 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) { @@ -18210,7 +18214,7 @@ namespace ts { } } } - } + }); links.exportsChecked = true; } @@ -18486,7 +18490,7 @@ namespace ts { } function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] { - const symbols = createMap(); + const symbols = createMap(); let memberFlags: ModifierFlags = ModifierFlags.None; if (isInsideWithStatementBody(location)) { @@ -18564,18 +18568,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 => { copySymbol(symbol, meaning); - } + }); } } } @@ -18986,8 +18989,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); } }); } @@ -19050,7 +19053,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; @@ -19400,7 +19403,7 @@ namespace ts { } function hasGlobalName(name: string): boolean { - return !!globals[name]; + return !!globals.get(name); } function getReferencedValueSymbol(reference: Identifier, startInDeclarationContainer?: boolean): Symbol { @@ -19457,14 +19460,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, @@ -19588,6 +19590,8 @@ namespace ts { function initializeTypeChecker() { // Bind all source files and propagate errors + performance.mark("heapBeforeBind"); + for (const file of host.getSourceFiles()) { bindSourceFile(file, compilerOptions); } @@ -19609,11 +19613,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; @@ -19639,6 +19641,8 @@ namespace ts { // Setup global builtins addToSymbolTable(globals, builtinGlobals, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0); + performance.mark("heapAfterBind"); + getSymbolLinks(undefinedSymbol).type = undefinedWideningType; getSymbolLinks(argumentsSymbol).type = getGlobalType("IArguments"); getSymbolLinks(unknownSymbol).type = unknownType; @@ -20311,7 +20315,7 @@ namespace ts { } function checkGrammarObjectLiteralExpression(node: ObjectLiteralExpression, inDestructuring: boolean) { - const seen = createMap(); + const seen = createMap(); const Property = 1; const GetAccessor = 2; const SetAccessor = 4; @@ -20374,17 +20378,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); @@ -20398,7 +20402,7 @@ namespace ts { } function checkGrammarJsxElement(node: JsxOpeningLikeElement) { - const seen = createMap(); + const seen = createSet(); for (const attr of node.attributes) { if (attr.kind === SyntaxKind.JsxSpreadAttribute) { continue; @@ -20406,8 +20410,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); @@ -20901,11 +20905,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/collections.ts b/src/compiler/collections.ts new file mode 100644 index 0000000000000..01f3da2b9d342 --- /dev/null +++ b/src/compiler/collections.ts @@ -0,0 +1,609 @@ +// NumberMap, StringMap, and StringSet shims +/* @internal */ +namespace ts { + // The global Map object. This may not be available, so we must test for it. + // Non-ES6 native maps don't support constructor arguments, so `createMap` must provide that functionality. + declare const Map: { new(): Map } | undefined; + const usingNativeMaps = typeof Map !== "undefined"; + // tslint:disable-next-line:no-in-operator + const usingES6NativeMaps = 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 ES6Map 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 }; + } + + /** + * Provides Map-like functionality for ES5 runtimes. + * This is intentionally *not* a full Map shim, and doesn't provide iterators (which aren't available for IE Maps anyway). + * We can only efficiently support strings and number keys, and iteration will always yield stringified keys. + */ + class ShimMap implements Map { + private data = createDictionaryModeObject(); + + /* + So long as `K extends string | number`, we can cast `key as string` and insert it into the map. + However, `forEach` will iterate over strings because values are stringified before being put in the map. + */ + + constructor() {} + + clear(): void { + this.data = createDictionaryModeObject(); + } + + delete(key: K): boolean { + const had = this.has(key); + if (had) { + delete this.data[key as string]; + } + return had; + } + + get(key: K): V { + return this.data[key as string]; + } + + has(key: K): boolean { + // tslint:disable-next-line:no-in-operator + return (key as string) in this.data; + } + + set(key: K, value: V): void { + this.data[key as string] = value; + } + + forEach(action: (value: V, key: string) => void): void { + for (const key in this.data) { + action(this.data[key], key); + } + } + } + + const MapCtr = usingNativeMaps ? Map : ShimMap; + /** + * In runtimes without Maps, this is implemented using an object. + * `pairs` is an optional list of entries to add to the new map. + */ + export function createMap(pairs?: [K, V][]): Map { + const map = new MapCtr(); + + if (pairs) { + for (const [key, value] of pairs) { + map.set(key, value); + } + } + + return map; + } + + 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`. + * Only works for strings because shims iterate with `for-in`. + */ + export const findInMap: (map: Map, getResult: (value: V, key: string) => U | undefined) => U | undefined = usingES6NativeMaps + ? (map: ES6Map, f: (value: V, key: string) => 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: string) => 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. + * Only works for strings because shims iterate with `for-in`. + */ + export const someInMap: (map: Map, predicate: (value: V, key: string) => boolean) => boolean = usingES6NativeMaps + ? (map: ES6Map, predicate: (value: V, key: string) => boolean) => + someInIterator(map.entries(), ([key, value]) => predicate(value, key)) + : (map: Map, predicate: (value: V, key: string) => 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. + * Only works for strings because shims iterate with `for-in`. + */ + export const someKeyInMap: (map: Map, predicate: (key: string) => boolean) => boolean = usingES6NativeMaps + ? (map: ES6Map, predicate: (key: string) => boolean) => someInIterator(map.keys(), predicate) + : (map: Map, predicate: (key: string) => 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 = usingES6NativeMaps + ? (map: ES6Map, 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);` + * Only works for strings because shims iterate with `for-in`. + */ + export const forEachKeyInMap: (map: Map, action: (key: string) => void) => void = usingES6NativeMaps + ? (map: ES6Map, action: (key: string) => void) => { + const iter: Iterator = map.keys(); + while (true) { + const { value: key, done } = iter.next(); + if (done) { + return; + } + action(key); + } + } + : (map: Map, action: (key: string) => 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 = createMap(); + // 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): boolean { + const had = this.has(value); + if (had) { + delete this.data[value]; + } + return had; + } + + 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() { + for (const _ in this.data) { + // TODO: GH#11734 + _; + return false; + } + return true; + } + } + + declare const Set: { new(): Set } | undefined; + const usingNativeSets = typeof Set !== "undefined"; + + const SetCtr = usingNativeSets ? Set : ShimStringSet; + export function createSet(): Set { + return new SetCtr(); + } + + /** 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 + + /** 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 = createMap(); + 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: V, key: K) => { + target.set(key, value); + }); + } + + /** + * Equivalent to `Array.from(map.keys())`. + * Only works for strings because shims iterate with `for-in`. + */ + export function keysOfMap(map: Map): string[] { + const keys: string[] = []; + 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 = createMap(); + 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 = createMap(); + 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 natural-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 + + /** Union of the `getSet` of each element in the array. */ + export function setAggregate(array: T[], getSet: (t: T) => Set): Set { + const result = createSet(); + 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 + + 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/commandLineParser.ts b/src/compiler/commandLineParser.ts index 686af79a6cca0..87337cb1e2e5c 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.ES2015, @@ -294,7 +294,7 @@ namespace ts { }, { name: "moduleResolution", - type: createMap({ + type: mapOfMapLike({ "node": ModuleResolutionKind.NodeJs, "classic": ModuleResolutionKind.Classic, }), @@ -403,7 +403,7 @@ namespace ts { type: "list", element: { name: "lib", - type: createMap({ + type: mapOfMapLike({ // JavaScript only "es5": "lib.es5.d.ts", "es6": "lib.es2015.d.ts", @@ -480,8 +480,8 @@ namespace ts { /* @internal */ export interface OptionNameMap { - optionNameMap: Map; - shortOptionNames: Map; + optionNameMap: Map; + shortOptionNames: Map; } /* @internal */ @@ -500,12 +500,12 @@ namespace ts { return optionNameMapCache; } - const optionNameMap = createMap(); - const shortOptionNames = createMap(); + const optionNameMap = createMap(); + const shortOptionNames = createMap(); 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); } }); @@ -515,16 +515,16 @@ namespace ts { /* @internal */ export function createCompilerDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType): Diagnostic { - const namesOfType = Object.keys(opt.type).map(key => `'${key}'`).join(", "); + const namesOfType = keysOfMap(opt.type).map(key => `'${key}'`).join(", "); 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)); @@ -577,13 +577,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)); } @@ -706,7 +706,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) @@ -718,7 +718,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; @@ -731,18 +731,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 = createMap(); const optionsNameMap = getOptionNameMap().optionNameMap; for (const name in options) { @@ -758,13 +757,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") { @@ -772,11 +771,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)); } } } @@ -784,7 +783,7 @@ namespace ts { } } } - return result; + return mapLikeOfMap(result); } } @@ -1025,8 +1024,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 { @@ -1062,8 +1061,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)); @@ -1172,12 +1172,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 = createMap(); // Wildcard paths (provided via the "includes" array in tsconfig.json) are stored in a // file map with a possibly case insensitive key. We use this map to store paths matched // via wildcard, and to handle extension priority. - const wildcardFileMap = createMap(); + const wildcardFileMap = createMap(); if (include) { include = validateSpecs(include, errors, /*allowTrailingRecursion*/ false); @@ -1191,7 +1191,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. @@ -1202,7 +1202,7 @@ namespace ts { if (fileNames) { for (const fileName of fileNames) { const file = combinePaths(basePath, fileName); - literalFileMap[keyMapper(file)] = file; + literalFileMap.set(keyMapper(file), file); } } @@ -1225,18 +1225,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) }; } @@ -1277,7 +1276,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 = createMap(); if (include !== undefined) { const recursiveKeys: string[] = []; for (const file of include) { @@ -1290,9 +1289,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); } @@ -1301,13 +1300,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; @@ -1321,13 +1320,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; } } @@ -1343,27 +1342,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 f207cf010e2a5..208b4d0931a82 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1,4 +1,5 @@ -/// +/// +/// /// /* @internal */ @@ -18,31 +19,11 @@ namespace ts { True = -1 } - const createObject = Object.create; - // More efficient to create a collator once and use its `compare` than to call `a.localeCompare(b)` many times. export const collator: { compare(a: string, b: string): number } = typeof Intl === "object" && typeof Intl.Collator === "function" ? new Intl.Collator() : undefined; - 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 = createMap(); return { get, set, @@ -54,39 +35,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 { @@ -707,242 +682,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 | number, 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. */ @@ -1041,10 +780,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: (string | number)[]): Diagnostic; diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index 6458183b23c90..0c3b2abf3f04d 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 = createSet(); } 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 1051c9adca961..d0568293eb7a5 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 = createSet(); isOwnFileEmit = !isBundledEmit; // Emit helpers from all the files @@ -2645,15 +2645,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; } } @@ -2702,7 +2703,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 c557e3514a5de..13f1230896e84 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1622,7 +1622,7 @@ namespace ts { // flag and setting a parent node. const react = createIdentifier(reactNamespace || "React"); react.flags &= ~NodeFlags.Synthesized; - // Set the parent that is in parse tree + // Set the parent that is in parse tree // this makes sure that parent chain is intact for checker to traverse complete scope tree react.parent = getParseTreeNode(parent); return react; @@ -2805,9 +2805,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 = createMap(); + copyMapEntriesFromTo(sourceRanges, destRanges); return destRanges; } @@ -2899,8 +2899,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 = createMap()); + tokenSourceMapRanges.set(token, range); return node; } @@ -2941,7 +2941,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); } /** @@ -3026,10 +3026,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 a5b02f98fd382..ea33473fe40a6 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -491,7 +491,7 @@ namespace ts { let currentToken: SyntaxKind; let sourceText: string; let nodeCount: number; - let identifiers: Map; + let identifiers: Map; let identifierCount: number; let parsingContext: ParsingContext; @@ -601,7 +601,7 @@ namespace ts { parseDiagnostics = []; parsingContext = 0; - identifiers = createMap(); + identifiers = createMap(); identifierCount = 0; nodeCount = 0; @@ -1104,7 +1104,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 a48eb117e28b9..6cb73e4d058c7 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 = createMap(); + marks = createMap(); + measures = createMap(); enabled = true; profilerStart = timestamp(); } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 532c90f38ec61..8a2b4d86671bf 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 = createSet(); 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 = createMap(); } 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 && 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 = createMap(); 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 = createMap(); let fileProcessingDiagnostics = createDiagnosticCollection(); // The below settings are to track if a .js file should be add to the program if loaded via searching under node_modules. @@ -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 = createMap(); // Track source files that are source files found by searching under node_modules, as these shouldn't be compiled. - const sourceFilesFoundSearchingNodeModules = createMap(); + const sourceFilesFoundSearchingNodeModules = createMap(); performance.mark("beforeProgram"); @@ -448,15 +448,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; @@ -592,7 +588,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, @@ -1132,8 +1128,8 @@ namespace ts { // Get source file from normalized fileName function findSourceFile(fileName: string, path: Path, isDefaultLib: 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)) { @@ -1142,20 +1138,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, isDefaultLib); processTypeReferenceDirectives(file); } - modulesWithElidedImports[file.path] = false; + modulesWithElidedImports.set(file.path, false); processImportedModules(file); } // 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 < maxNodeModuleJsDepth) { - modulesWithElidedImports[file.path] = false; + modulesWithElidedImports.set(file.path, false); processImportedModules(file); } } @@ -1164,7 +1160,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)); @@ -1176,7 +1172,7 @@ namespace ts { filesByName.set(path, file); if (file) { - sourceFilesFoundSearchingNodeModules[path] = (currentNodeModulesDepth > 0); + sourceFilesFoundSearchingNodeModules.set(path, currentNodeModulesDepth > 0); file.path = path; if (host.useCaseSensitiveFileNames()) { @@ -1237,7 +1233,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; } @@ -1274,7 +1270,7 @@ namespace ts { } if (saveResolution) { - resolvedTypeReferenceDirectives[typeReferenceDirective] = resolvedTypeReferenceDirective; + resolvedTypeReferenceDirectives.set(typeReferenceDirective, resolvedTypeReferenceDirective); } } @@ -1294,7 +1290,7 @@ namespace ts { function processImportedModules(file: SourceFile) { collectExternalModuleReferences(file); if (file.imports.length || file.moduleAugmentations.length) { - file.resolvedModules = createMap(); + file.resolvedModules = createMap(); const moduleNames = map(concatenate(file.imports, file.moduleAugmentations), getTextOfLiteral); const resolutions = resolveModuleNamesWorker(moduleNames, getNormalizedAbsolutePath(file.fileName, currentDirectory)); Debug.assert(resolutions.length === moduleNames.length); @@ -1325,7 +1321,7 @@ namespace ts { const shouldAddFile = resolvedFileName && !getResolutionDiagnostic(options, resolution) && !options.noResolve && i < file.imports.length && !elideImport; if (elideImport) { - modulesWithElidedImports[file.path] = true; + modulesWithElidedImports.set(file.path, true); } else if (shouldAddFile) { const path = toPath(resolvedFileName, currentDirectory, getCanonicalFileName); diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 1c0baa457f4af..dec76d2b926b6 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 46db5188e339c..fcf85aa86a676 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 13bbfc2ab153f..5df1976f98e34 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -237,25 +237,25 @@ namespace ts { const useNonPollingWatchers = process.env["TSC_NONPOLLING_WATCHER"]; function createWatchedFileSet() { - const dirWatchers = createMap(); + const dirWatchers = createMap(); // One file can have multiple watchers - const fileWatcherCallbacks = createMap(); + const fileWatcherCallbacks = createMap(); 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; @@ -266,7 +266,7 @@ namespace ts { (eventName: string, relativeFileName: string) => fileEventHandler(eventName, relativeFileName, dirPath) ); watcher.referenceCount = 1; - dirWatchers[dirPath] = watcher; + dirWatchers.set(dirPath, watcher); return; } @@ -296,9 +296,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 callbacks) { + fileCallback(fileName); + } } } } diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index ee9c081e6ebd8..4534564a854a4 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -12,14 +12,14 @@ /* @internal */ namespace ts { - const moduleTransformerMap = createMap({ - [ModuleKind.ES2015]: transformES2015Module, - [ModuleKind.System]: transformSystemModule, - [ModuleKind.AMD]: transformModule, - [ModuleKind.CommonJS]: transformModule, - [ModuleKind.UMD]: transformModule, - [ModuleKind.None]: transformModule, - }); + const moduleTransformerMap = createMap([ + [ModuleKind.ES2015, transformES2015Module], + [ModuleKind.System, transformSystemModule], + [ModuleKind.AMD, transformModule], + [ModuleKind.CommonJS, transformModule], + [ModuleKind.UMD, transformModule], + [ModuleKind.None, transformModule], + ]); const enum SyntaxKindFeatureFlags { Substitution = 1 << 0, @@ -129,7 +129,7 @@ namespace ts { transformers.push(transformGenerators); } - transformers.push(moduleTransformerMap[moduleKind] || moduleTransformerMap[ModuleKind.None]); + transformers.push(moduleTransformerMap.get(moduleKind) || moduleTransformerMap.get(ModuleKind.None)); // The ES5 transformer is last so that it can substitute expressions like `exports.default` // for ES3. diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 25c15305c4aa2..4e0b0749d3a97 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.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