diff --git a/Jakefile.js b/Jakefile.js index a5650a56b16dc..bbe0fb504c009 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -36,6 +36,7 @@ var compilerSources = [ "core.ts", "performance.ts", "sys.ts", + "dataStructures.ts", "types.ts", "scanner.ts", "parser.ts", @@ -57,6 +58,7 @@ var servicesSources = [ "core.ts", "performance.ts", "sys.ts", + "dataStructures.ts", "types.ts", "scanner.ts", "parser.ts", diff --git a/scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.ts b/scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.ts index 1ae3725fff211..0bc0d8dee92a8 100644 --- a/scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.ts +++ b/scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.ts @@ -52,7 +52,7 @@ function importDefinitelyTypedTest(tscPath: string, rwcTestPath: string, testCas cmd += " @" + responseFile; } - let testDirectoryName = testCaseName + "_" + Math.floor((Math.random() * 10000) + 1); + let testDirectoryName = testCaseName + "_" + Math.floor((Math.random() * 10000) + 1); let testDirectoryPath = path.join(process.env["temp"], testDirectoryName); if (fs.existsSync(testDirectoryPath)) { throw new Error("Could not create test directory"); diff --git a/scripts/processDiagnosticMessages.ts b/scripts/processDiagnosticMessages.ts index 26632ba6bab3c..2e0a3fedc2dc5 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,12 @@ function checkForUniqueCodes(messages: string[], diagnosticTable: InputDiagnosti } } -function buildUniqueNameMap(names: string[]): ts.Map { - var nameMap: ts.Map = {}; - +function buildUniqueNameMap(names: string[]): ts.StringMap { var uniqueNames = NameGenerator.ensureUniqueness(names, /* isCaseSensitive */ false, /* isFixed */ undefined); - - for (var i = 0; i < names.length; i++) { - nameMap[names[i]] = uniqueNames[i]; - } - - return nameMap; + return ts.createStringMapFromKeysAndValues(names, uniqueNames); } -function buildInfoFileOutput(messageTable: InputDiagnosticMessageTable, nameMap: ts.Map): string { +function buildInfoFileOutput(messageTable: InputDiagnosticMessageTable, nameMap: ts.StringMap): string { var result = '// \r\n' + '/// \r\n' + @@ -91,7 +84,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 +100,14 @@ function buildInfoFileOutput(messageTable: InputDiagnosticMessageTable, nameMap: return result; } -function buildDiagnosticMessageOutput(messageTable: InputDiagnosticMessageTable, nameMap: ts.Map): string { +function buildDiagnosticMessageOutput(messageTable: InputDiagnosticMessageTable, nameMap: ts.StringMap): 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 8d8666a67abac..ffe3f1ba3d4fc 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -125,7 +125,7 @@ namespace ts { let symbolCount = 0; let Symbol: { new (flags: SymbolFlags, name: string): Symbol }; - let classifiableNames: Map; + let classifiableNames: StringSet; const unreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; const reportedUnreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; @@ -135,7 +135,7 @@ namespace ts { options = opts; languageVersion = getEmitScriptTarget(options); inStrictMode = !!file.externalModuleIndicator; - classifiableNames = {}; + classifiableNames = new StringSet(); symbolCount = 0; Symbol = objectAllocator.getSymbolConstructor(); @@ -183,11 +183,11 @@ namespace ts { symbol.declarations.push(node); if (symbolFlags & SymbolFlags.HasExports && !symbol.exports) { - symbol.exports = {}; + symbol.exports = new StringMap(); } if (symbolFlags & SymbolFlags.HasMembers && !symbol.members) { - symbol.members = {}; + symbol.members = new StringMap(); } if (symbolFlags & SymbolFlags.Value) { @@ -318,12 +318,10 @@ namespace ts { // Otherwise, we'll be merging into a compatible existing symbol (for example when // you have multiple 'vars' with the same name in the same container). In this case // just add this node into the declarations list of the symbol. - symbol = hasProperty(symbolTable, name) - ? symbolTable[name] - : (symbolTable[name] = createSymbol(SymbolFlags.None, name)); + symbol = getOrUpdate(symbolTable, name, () => createSymbol(SymbolFlags.None, name)); if (name && (includes & SymbolFlags.Classifiable)) { - classifiableNames[name] = name; + classifiableNames.add(name); } if (symbol.flags & excludes) { @@ -434,7 +432,7 @@ namespace ts { if (containerFlags & ContainerFlags.IsContainer) { container = blockScopeContainer = node; if (containerFlags & ContainerFlags.HasLocals) { - container.locals = {}; + container.locals = new StringMap(); } addToContainerChain(container); } @@ -1399,7 +1397,7 @@ namespace ts { const typeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, "__type"); addDeclarationToSymbol(typeLiteralSymbol, node, SymbolFlags.TypeLiteral); - typeLiteralSymbol.members = { [symbol.name]: symbol }; + typeLiteralSymbol.members = createStringMap(symbol.name, symbol); } function bindObjectLiteralExpression(node: ObjectLiteralExpression) { @@ -1409,7 +1407,7 @@ namespace ts { } if (inStrictMode) { - const seen: Map = {}; + const seen = new StringMap(); for (const prop of node.properties) { if (prop.name.kind !== SyntaxKind.Identifier) { @@ -1430,9 +1428,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; } @@ -1465,7 +1463,7 @@ namespace ts { // fall through. default: if (!blockScopeContainer.locals) { - blockScopeContainer.locals = {}; + blockScopeContainer.locals = new StringMap(); addToContainerChain(blockScopeContainer); } declareSymbol(blockScopeContainer.locals, undefined, node, symbolFlags, symbolExcludes); @@ -1925,7 +1923,7 @@ namespace ts { } } - file.symbol.globalExports = file.symbol.globalExports || {}; + file.symbol.globalExports = file.symbol.globalExports || new StringMap(); declareSymbol(file.symbol.globalExports, file.symbol, node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); } @@ -1978,7 +1976,7 @@ namespace ts { else { return; } - assignee.symbol.members = assignee.symbol.members || {}; + assignee.symbol.members = assignee.symbol.members || new StringMap(); // It's acceptable for multiple 'this' assignments of the same identifier to occur declareSymbol(assignee.symbol.members, assignee.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes & ~SymbolFlags.Property); } @@ -1997,15 +1995,13 @@ 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 = {}; - } + funcSymbol.members = funcSymbol.members || new StringMap(); // Declare the method/property declareSymbol(funcSymbol.members, funcSymbol, leftSideOfAssignment, SymbolFlags.Property, SymbolFlags.PropertyExcludes); @@ -2037,7 +2033,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); } } @@ -2053,14 +2049,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 (hasProperty(symbol.exports, prototypeSymbol.name)) { + const existingPrototypeSymbol = symbol.exports.get(prototypeSymbol.name); + if (existingPrototypeSymbol) { if (node.name) { node.name.parent = node; } - file.bindDiagnostics.push(createDiagnosticForNode(symbol.exports[prototypeSymbol.name].declarations[0], + file.bindDiagnostics.push(createDiagnosticForNode(existingPrototypeSymbol.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 1f2eaea833df0..b1f3ddf33d1e5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44,7 +44,7 @@ namespace ts { let symbolCount = 0; const emptyArray: any[] = []; - const emptySymbols: SymbolTable = {}; + const emptySymbols: SymbolTable = new StringMap(); const compilerOptions = host.getCompilerOptions(); const languageVersion = compilerOptions.target || ScriptTarget.ES3; @@ -106,11 +106,11 @@ namespace ts { isOptionalParameter }; - const tupleTypes: Map = {}; - const unionTypes: Map = {}; - const intersectionTypes: Map = {}; - const stringLiteralTypes: Map = {}; - const numericLiteralTypes: Map = {}; + const tupleTypes = new StringMap(); + const unionTypes = new StringMap(); + const intersectionTypes = new StringMap(); + const stringLiteralTypes = new StringMap(); + const numericLiteralTypes = new StringMap(); const unknownSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "unknown"); const resolvingSymbol = createSymbol(SymbolFlags.Transient, "__resolving__"); @@ -132,7 +132,7 @@ namespace ts { const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); const emptyGenericType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); - emptyGenericType.instantiations = {}; + emptyGenericType.instantiations = new StringMap(); const anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); // The anyFunctionType contains the anyFunctionType by definition. The flag is further propagated @@ -146,7 +146,7 @@ namespace ts { const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true); - const globals: SymbolTable = {}; + const globals: SymbolTable = new StringMap(); /** * List of every ambient module with a "*" wildcard. * Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches. @@ -210,7 +210,7 @@ namespace ts { const mergedSymbols: Symbol[] = []; const symbolLinks: SymbolLinks[] = []; const nodeLinks: NodeLinks[] = []; - const flowLoopCaches: Map[] = []; + const flowLoopCaches: StringMap[] = []; const flowLoopNodes: FlowNode[] = []; const flowLoopKeys: string[] = []; const flowLoopTypes: Type[][] = []; @@ -283,37 +283,37 @@ namespace ts { NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy, } - const typeofEQFacts: Map = { - "string": TypeFacts.TypeofEQString, - "number": TypeFacts.TypeofEQNumber, - "boolean": TypeFacts.TypeofEQBoolean, - "symbol": TypeFacts.TypeofEQSymbol, - "undefined": TypeFacts.EQUndefined, - "object": TypeFacts.TypeofEQObject, - "function": TypeFacts.TypeofEQFunction - }; - - const typeofNEFacts: Map = { - "string": TypeFacts.TypeofNEString, - "number": TypeFacts.TypeofNENumber, - "boolean": TypeFacts.TypeofNEBoolean, - "symbol": TypeFacts.TypeofNESymbol, - "undefined": TypeFacts.NEUndefined, - "object": TypeFacts.TypeofNEObject, - "function": TypeFacts.TypeofNEFunction - }; - - const typeofTypesByName: Map = { - "string": stringType, - "number": numberType, - "boolean": booleanType, - "symbol": esSymbolType, - "undefined": undefinedType - }; + const typeofEQFacts = new StringMap([ + ["string", TypeFacts.TypeofEQString], + ["number", TypeFacts.TypeofEQNumber], + ["boolean", TypeFacts.TypeofEQBoolean], + ["symbol", TypeFacts.TypeofEQSymbol], + ["undefined", TypeFacts.EQUndefined], + ["object", TypeFacts.TypeofEQObject], + ["function", TypeFacts.TypeofEQFunction] + ]); + + const typeofNEFacts = new StringMap([ + ["string", TypeFacts.TypeofNEString], + ["number", TypeFacts.TypeofNENumber], + ["boolean", TypeFacts.TypeofNEBoolean], + ["symbol", TypeFacts.TypeofNESymbol], + ["undefined", TypeFacts.NEUndefined], + ["object", TypeFacts.TypeofNEObject], + ["function", TypeFacts.TypeofNEFunction] + ]); + + const typeofTypesByName = new StringMap([ + ["string", stringType], + ["number", numberType], + ["boolean", booleanType], + ["symbol", esSymbolType], + ["undefined", undefinedType] + ]); let jsxElementType: ObjectType; /** Things we lazy load from the JSX namespace */ - const jsxTypes: Map = {}; + const jsxTypes = new StringMap(); const JsxNames = { JSX: "JSX", IntrinsicElements: "IntrinsicElements", @@ -324,10 +324,11 @@ namespace ts { IntrinsicClassAttributes: "IntrinsicClassAttributes" }; - const subtypeRelation: Map = {}; - const assignableRelation: Map = {}; - const comparableRelation: Map = {}; - const identityRelation: Map = {}; + type Relation = StringMap; + const subtypeRelation: Relation = new StringMap(); + const assignableRelation: Relation = new StringMap(); + const comparableRelation: Relation = new StringMap(); + const identityRelation: Relation = new StringMap(); // This is for caching the result of getSymbolDisplayBuilder. Do not access directly. let _displayBuilder: SymbolDisplayBuilder; @@ -341,9 +342,7 @@ namespace ts { ResolvedReturnType } - const builtinGlobals: SymbolTable = { - [undefinedSymbol.name]: undefinedSymbol - }; + const builtinGlobals: SymbolTable = createStringMap(undefinedSymbol.name, undefinedSymbol); initializeTypeChecker(); @@ -426,11 +425,11 @@ namespace ts { target.declarations.push(node); }); if (source.members) { - if (!target.members) target.members = {}; + if (!target.members) target.members = new StringMap(); mergeSymbolTable(target.members, source.members); } if (source.exports) { - if (!target.exports) target.exports = {}; + if (!target.exports) target.exports = new StringMap(); mergeSymbolTable(target.exports, source.exports); } recordMergedSymbol(target, source); @@ -448,30 +447,23 @@ namespace ts { } function cloneSymbolTable(symbolTable: SymbolTable): SymbolTable { - const result: SymbolTable = {}; - for (const id in symbolTable) { - if (hasProperty(symbolTable, id)) { - result[id] = symbolTable[id]; - } - } - return result; + return new StringMap(symbolTable); } function mergeSymbolTable(target: SymbolTable, source: SymbolTable) { - for (const id in source) { - if (hasProperty(source, id)) { - if (!hasProperty(target, id)) { - target[id] = source[id]; - } - else { - let symbol = target[id]; - if (!(symbol.flags & SymbolFlags.Merged)) { - target[id] = symbol = cloneSymbol(symbol); - } - mergeSymbol(symbol, source[id]); + source.forEach((sourceSymbol, id) => { + let targetSymbol = target.get(id); + if (targetSymbol) { + if (!(targetSymbol.flags & SymbolFlags.Merged)) { + targetSymbol = cloneSymbol(targetSymbol); + target.set(id, targetSymbol); } + mergeSymbol(targetSymbol, sourceSymbol); } - } + else { + target.set(id, sourceSymbol); + } + }); } function mergeModuleAugmentation(moduleName: LiteralExpression): void { @@ -512,17 +504,16 @@ namespace ts { } function addToSymbolTable(target: SymbolTable, source: SymbolTable, message: DiagnosticMessage) { - for (const id in source) { - if (hasProperty(source, id)) { - if (hasProperty(target, id)) { - // Error on redeclarations - forEach(target[id].declarations, addDeclarationDiagnostic(id, message)); - } - else { - target[id] = source[id]; - } + source.forEach((sourceSymbol, id) => { + const targetSymbol = target.get(id); + if (targetSymbol) { + // Error on redeclarations + forEach(targetSymbol.declarations, addDeclarationDiagnostic(id, message)); } - } + else { + target.set(id, sourceSymbol); + } + }); function addDeclarationDiagnostic(id: string, message: DiagnosticMessage) { return (declaration: Declaration) => diagnostics.add(createDiagnosticForNode(declaration, message, id)); @@ -544,20 +535,27 @@ namespace ts { return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(node); } - function getSymbol(symbols: SymbolTable, name: string, meaning: SymbolFlags): Symbol { - if (meaning && hasProperty(symbols, name)) { - const symbol = symbols[name]; - Debug.assert((symbol.flags & SymbolFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); - if (symbol.flags & meaning) { + function getSymbol(symbols: SymbolTable, name: string, meaning: SymbolFlags): Symbol | undefined { + if (!meaning) { + return undefined; + } + + const symbol = symbols.get(name); + if (!symbol) { + return undefined; + } + + Debug.assert((symbol.flags & SymbolFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); + if (symbol.flags & meaning) { + return symbol; + } + + if ((symbol.flags & SymbolFlags.Alias)) { + const target = resolveAlias(symbol); + // Unknown symbol means an error occurred in alias resolution, treat it as positive answer to avoid cascading errors + if (target === unknownSymbol || target.flags & meaning) { return symbol; } - if (symbol.flags & SymbolFlags.Alias) { - const target = resolveAlias(symbol); - // Unknown symbol means an error occurred in alias resolution, treat it as positive answer to avoid cascading errors - if (target === unknownSymbol || target.flags & meaning) { - return symbol; - } - } } // return undefined if we can't find a symbol. } @@ -725,7 +723,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; @@ -744,9 +742,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 (hasProperty(moduleExports, name) && - moduleExports[name].flags === SymbolFlags.Alias && - getDeclarationOfKind(moduleExports[name], SyntaxKind.ExportSpecifier)) { + const exported = moduleExports.get(name); + if (exported && + exported.flags === SymbolFlags.Alias && + getDeclarationOfKind(exported, SyntaxKind.ExportSpecifier)) { break; } } @@ -1047,11 +1046,12 @@ namespace ts { const moduleSymbol = resolveExternalModuleName(node, (node.parent).moduleSpecifier); if (moduleSymbol) { + let exportEqual: Symbol; const exportDefaultSymbol = isShorthandAmbientModule(moduleSymbol.valueDeclaration) ? moduleSymbol : - moduleSymbol.exports["export="] ? - getPropertyOfType(getTypeOfSymbol(moduleSymbol.exports["export="]), "default") : - resolveSymbol(moduleSymbol.exports["default"]); + (exportEqual = getExportAssignmentSymbol(moduleSymbol)) ? + getPropertyOfType(getTypeOfSymbol(exportEqual), "default") : + resolveSymbol(moduleSymbol.exports.get("default")); if (!exportDefaultSymbol && !allowSyntheticDefaultImports) { error(node.name, Diagnostics.Module_0_has_no_default_export, symbolToString(moduleSymbol)); @@ -1102,8 +1102,9 @@ namespace ts { function getExportOfModule(symbol: Symbol, name: string): Symbol { if (symbol.flags & SymbolFlags.Module) { const exports = getExportsOfSymbol(symbol); - if (hasProperty(exports, name)) { - return resolveSymbol(exports[name]); + const exported = exports.get(name); + if (exported) { + return resolveSymbol(exported); } } } @@ -1129,7 +1130,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 && hasExportAssignmentSymbol(moduleSymbol)) { symbolFromVariable = getPropertyOfType(getTypeOfSymbol(targetSymbol), name.text); } else { @@ -1378,7 +1379,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(getExportAssignmentSymbol(moduleSymbol))) || moduleSymbol; } // An external module with an 'export =' declaration may be referenced as an ES6 module provided the 'export =' @@ -1394,7 +1395,11 @@ namespace ts { } function hasExportAssignmentSymbol(moduleSymbol: Symbol): boolean { - return moduleSymbol.exports["export="] !== undefined; + return moduleSymbol.exports.has("export="); + } + + function getExportAssignmentSymbol(moduleSymbol: Symbol): Symbol { + return moduleSymbol.exports.get("export="); } function getExportsOfModuleAsArray(moduleSymbol: Symbol): Symbol[] { @@ -1419,25 +1424,28 @@ 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" && !hasProperty(target, id)) { - target[id] = source[id]; + function extendExportSymbols(target: SymbolTable, source: SymbolTable, lookupTable?: StringMap, exportNode?: ExportDeclaration) { + if (!source) { + return; + } + + source.forEach((sourceSymbol, id) => { + if (id === "default") { + return; + } + + const targetSymbol = target.get(id); + if (!targetSymbol) { + target.set(id, sourceSymbol); if (lookupTable && exportNode) { - lookupTable[id] = { - specifierText: getTextOfNode(exportNode.moduleSpecifier) - } as ExportCollisionTracker; + lookupTable.set(id, { specifierText: getTextOfNode(exportNode.moduleSpecifier) } as ExportCollisionTracker); } } - else if (lookupTable && exportNode && id !== "default" && hasProperty(target, id) && resolveSymbol(target[id]) !== resolveSymbol(source[id])) { - if (!lookupTable[id].exportsWithDuplicate) { - lookupTable[id].exportsWithDuplicate = [exportNode]; - } - else { - lookupTable[id].exportsWithDuplicate.push(exportNode); - } + else if (lookupTable && exportNode && resolveSymbol(targetSymbol) !== resolveSymbol(sourceSymbol)) { + const lookupSymbol = lookupTable.get(id); + (lookupSymbol.exportsWithDuplicate || (lookupSymbol.exportsWithDuplicate = [])).push(exportNode); } - } + }); } function getExportsForModule(moduleSymbol: Symbol): SymbolTable { @@ -1453,10 +1461,10 @@ namespace ts { visitedSymbols.push(symbol); const symbols = cloneSymbolTable(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: SymbolTable = {}; - const lookupTable: Map = {}; + const nestedSymbols: SymbolTable = new StringMap(); + const lookupTable = new StringMap(); for (const node of exportStars.declarations) { const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier); const exportedSymbols = visit(resolvedModule); @@ -1467,21 +1475,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) || hasProperty(symbols, id)) { - continue; + if (id === "export=" || !(exportsWithDuplicate && exportsWithDuplicate.length) || symbols.has(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; @@ -1575,17 +1582,11 @@ namespace ts { function getNamedMembers(members: SymbolTable): Symbol[] { let result: Symbol[]; - for (const id in members) { - if (hasProperty(members, id)) { - if (!isReservedMemberName(id)) { - if (!result) result = []; - const symbol = members[id]; - if (symbolIsValue(symbol)) { - result.push(symbol); - } - } + members.forEach((symbol, id) => { + if (!isReservedMemberName(id) && symbolIsValue(symbol)) { + (result || (result = [])).push(symbol); } - } + }); return result || emptyArray; } @@ -1658,12 +1659,12 @@ namespace ts { } // If symbol is directly available by its name in the symbol table - if (isAccessible(lookUp(symbols, symbol.name))) { + if (isAccessible(symbols.get(symbol.name))) { return [symbol]; } // Check if symbol is any of the alias - return forEachValue(symbols, symbolFromSymbolTable => { + return findInStringMap(symbols, symbolFromSymbolTable => { if (symbolFromSymbolTable.flags & SymbolFlags.Alias && symbolFromSymbolTable.name !== "export=" && !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)) { @@ -1697,13 +1698,14 @@ namespace ts { function needsQualification(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags) { let qualify = false; forEachSymbolTableInScope(enclosingDeclaration, symbolTable => { + let symbolFromSymbolTable = symbolTable.get(symbol.name); + // If symbol of this name is not available in the symbol table we are ok - if (!hasProperty(symbolTable, symbol.name)) { + if (!symbolFromSymbolTable) { // Continue to the next symbol table return false; } // If the symbol with this name is present it should refer to the symbol - let symbolFromSymbolTable = symbolTable[symbol.name]; if (symbolFromSymbolTable === symbol) { // No need to qualify return true; @@ -2421,7 +2423,8 @@ namespace ts { } writeIndexSignature(resolved.stringIndexInfo, SyntaxKind.StringKeyword); writeIndexSignature(resolved.numberIndexInfo, SyntaxKind.NumberKeyword); - for (const p of resolved.properties) { + const sortedProperties = sortInV8ObjectInsertionOrder(resolved.properties, p => p.name); + for (const p of sortedProperties) { const t = getTypeOfSymbol(p); if (p.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(t).length) { const signatures = getSignaturesOfType(t, SignatureKind.Call); @@ -3121,7 +3124,7 @@ namespace ts { // Return the type implied by an object binding pattern function getTypeFromObjectBindingPattern(pattern: BindingPattern, includePatternInType: boolean): Type { - const members: SymbolTable = {}; + const members: SymbolTable = new StringMap(); let hasComputedProperties = false; forEach(pattern.elements, e => { const name = e.propertyName || e.name; @@ -3136,7 +3139,7 @@ namespace ts { const symbol = createSymbol(flags, text); symbol.type = getTypeFromBindingElement(e, includePatternInType); symbol.bindingElement = e; - members[symbol.name] = symbol; + members.set(symbol.name, symbol); }); const result = createAnonymousType(undefined, members, emptyArray, emptyArray, undefined, undefined); if (includePatternInType) { @@ -3709,8 +3712,7 @@ namespace ts { type.typeParameters = concatenate(outerTypeParameters, localTypeParameters); type.outerTypeParameters = outerTypeParameters; type.localTypeParameters = localTypeParameters; - (type).instantiations = {}; - (type).instantiations[getTypeListId(type.typeParameters)] = type; + (type).instantiations = createStringMap(getTypeListId(type.typeParameters), type); (type).target = type; (type).typeArguments = type.typeParameters; type.thisType = createType(TypeFlags.TypeParameter | TypeFlags.ThisType); @@ -3751,8 +3753,7 @@ namespace ts { if (typeParameters) { // Initialize the instantiation cache for generic type aliases. The declared type corresponds to // an instantiation of the type alias with the type parameters supplied as type arguments. - links.instantiations = {}; - links.instantiations[getTypeListId(links.typeParameters)] = type; + links.instantiations = createStringMap(getTypeListId(links.typeParameters), type); } } else { @@ -3772,7 +3773,7 @@ namespace ts { return expr.kind === SyntaxKind.NumericLiteral || expr.kind === SyntaxKind.PrefixUnaryExpression && (expr).operator === SyntaxKind.MinusToken && (expr).operand.kind === SyntaxKind.NumericLiteral || - expr.kind === SyntaxKind.Identifier && hasProperty(symbol.exports, (expr).text); + expr.kind === SyntaxKind.Identifier && symbol.exports.has((expr).text); } function enumHasLiteralMembers(symbol: Symbol) { @@ -3795,15 +3796,15 @@ namespace ts { enumType.symbol = symbol; if (enumHasLiteralMembers(symbol)) { const memberTypeList: Type[] = []; - const memberTypes: Map = {}; + const memberTypes = new NumberMap(); for (const declaration of enumType.symbol.declarations) { if (declaration.kind === SyntaxKind.EnumDeclaration) { computeEnumMemberValues(declaration); for (const member of (declaration).members) { const memberSymbol = getSymbolOfNode(member); const value = getEnumMemberValue(member); - if (!memberTypes[value]) { - const memberType = memberTypes[value] = createType(TypeFlags.EnumLiteral); + if (!memberTypes.has(value)) { + const memberType = setAndReturn(memberTypes, value, createType(TypeFlags.EnumLiteral)); memberType.symbol = memberSymbol; memberType.baseType = enumType; memberType.text = "" + value; @@ -3816,7 +3817,7 @@ namespace ts { if (memberTypeList.length > 1) { enumType.flags |= TypeFlags.Union; (enumType).types = memberTypeList; - unionTypes[getTypeListId(memberTypeList)] = enumType; + unionTypes.set(getTypeListId(memberTypeList), enumType); } } } @@ -3828,7 +3829,7 @@ namespace ts { if (!links.declaredType) { const enumType = getDeclaredTypeOfEnum(getParentOfSymbol(symbol)); links.declaredType = enumType.flags & TypeFlags.Union ? - enumType.memberTypes[getEnumMemberValue(symbol.valueDeclaration)] : + enumType.memberTypes.get(getEnumMemberValue(symbol.valueDeclaration)) : enumType; } return links.declaredType; @@ -3958,28 +3959,20 @@ namespace ts { } function createSymbolTable(symbols: Symbol[]): SymbolTable { - const result: SymbolTable = {}; - for (const symbol of symbols) { - result[symbol.name] = symbol; - } - return result; + return createStringMapFromValues(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: SymbolTable = {}; - for (const symbol of symbols) { - result[symbol.name] = mappingThisOnly && isIndependentMember(symbol) ? symbol : instantiateSymbol(symbol, mapper); - } - return result; + return createStringMapFromArray(symbols, + symbol => symbol.name, + symbol => mappingThisOnly && isIndependentMember(symbol) ? symbol : instantiateSymbol(symbol, mapper)); } function addInheritedMembers(symbols: SymbolTable, baseSymbols: Symbol[]) { for (const s of baseSymbols) { - if (!hasProperty(symbols, s.name)) { - symbols[s.name] = s; - } + setButDontOverride(symbols, s.name, () => s); } } @@ -3987,8 +3980,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); } @@ -4091,11 +4084,12 @@ namespace ts { } function createTupleTypeMemberSymbols(memberTypes: Type[]): SymbolTable { - const members: SymbolTable = {}; + const members: SymbolTable = new StringMap(); for (let i = 0; i < memberTypes.length; i++) { - const symbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "" + i); + const name = "" + i; + const symbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, name); symbol.type = memberTypes[i]; - members[i] = symbol; + members.set(name, symbol); } return members; } @@ -4239,8 +4233,8 @@ namespace ts { } else if (symbol.flags & SymbolFlags.TypeLiteral) { const members = symbol.members; - const callSignatures = getSignaturesOfSymbol(members["__call"]); - const constructSignatures = getSignaturesOfSymbol(members["__new"]); + const callSignatures = getSignaturesOfSymbol(members.get("__call")); + const constructSignatures = getSignaturesOfSymbol(members.get("__new")); const stringIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.String); const numberIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.Number); setObjectTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); @@ -4254,7 +4248,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); } @@ -4313,11 +4307,9 @@ namespace ts { function getPropertyOfObjectType(type: Type, name: string): Symbol { if (type.flags & TypeFlags.ObjectType) { const resolved = resolveStructuredTypeMembers(type); - if (hasProperty(resolved.members, name)) { - const symbol = resolved.members[name]; - if (symbolIsValue(symbol)) { - return symbol; - } + const symbol = resolved.members.get(name); + if (symbol && symbolIsValue(symbol)) { + return symbol; } } } @@ -4446,15 +4438,8 @@ namespace ts { } function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: string): Symbol { - const properties = type.resolvedProperties || (type.resolvedProperties = {}); - if (hasProperty(properties, name)) { - return properties[name]; - } - const property = createUnionOrIntersectionProperty(type, name); - if (property) { - properties[name] = property; - } - return property; + const properties = type.resolvedProperties || (type.resolvedProperties = new StringMap()); + return getOrUpdate(properties, name, () => createUnionOrIntersectionProperty(type, name)); } /** @@ -4469,10 +4454,10 @@ namespace ts { type = getApparentType(type); if (type.flags & TypeFlags.ObjectType) { const resolved = resolveStructuredTypeMembers(type); - if (hasProperty(resolved.members, name)) { - const symbol = resolved.members[name]; - if (symbolIsValue(symbol)) { - return symbol; + const property = resolved.members.get(name); + if (property) { + if (symbolIsValue(property)) { + return property; } } if (resolved === anyFunctionType || resolved.callSignatures.length || resolved.constructSignatures.length) { @@ -4569,13 +4554,11 @@ namespace ts { } function symbolsToArray(symbols: SymbolTable): Symbol[] { - const result: Symbol[] = []; - for (const id in symbols) { + return mapAndFilterStringMap(symbols, (symbol, id) => { if (!isReservedMemberName(id)) { - result.push(symbols[id]); + return symbol; } - } - return result; + }); } function isJSDocOptionalParameter(node: ParameterDeclaration) { @@ -4866,7 +4849,7 @@ namespace ts { } function getIndexSymbol(symbol: Symbol): Symbol { - return symbol.members["__index"]; + return symbol.members.get("__index"); } function getIndexDeclarationOfSymbol(symbol: Symbol, kind: IndexKind): SignatureDeclaration { @@ -4977,11 +4960,11 @@ namespace ts { function createTypeReference(target: GenericType, typeArguments: Type[]): TypeReference { const id = getTypeListId(typeArguments); - let type = target.instantiations[id]; + let type = target.instantiations.get(id); if (!type) { const propagatedFlags = typeArguments ? getPropagatingFlagsOfTypes(typeArguments, /*excludeKinds*/ 0) : 0; const flags = TypeFlags.Reference | propagatedFlags; - type = target.instantiations[id] = createObjectType(flags, target.symbol); + type = setAndReturn(target.instantiations, id, createObjectType(flags, target.symbol)); type.target = target; type.typeArguments = typeArguments; } @@ -5023,7 +5006,7 @@ namespace ts { } const typeArguments = map(node.typeArguments, getTypeFromTypeNode); const id = getTypeListId(typeArguments); - return links.instantiations[id] || (links.instantiations[id] = instantiateType(type, createTypeMapper(typeParameters, typeArguments))); + return getOrUpdate(links.instantiations, id, () => instantiateType(type, createTypeMapper(typeParameters, typeArguments))); } if (node.typeArguments) { error(node, Diagnostics.Type_0_is_not_generic, symbolToString(symbol)); @@ -5231,8 +5214,7 @@ namespace ts { } function createTupleType(elementTypes: Type[]) { - const id = getTypeListId(elementTypes); - return tupleTypes[id] || (tupleTypes[id] = createNewTupleType(elementTypes)); + return getOrUpdate(tupleTypes, getTypeListId(elementTypes), () => createNewTupleType(elementTypes)); } function createNewTupleType(elementTypes: Type[]) { @@ -5371,10 +5353,10 @@ namespace ts { return typeSet[0]; } const id = getTypeListId(typeSet); - let type = unionTypes[id]; + let type = unionTypes.get(id); if (!type) { const propagatedFlags = getPropagatingFlagsOfTypes(typeSet, /*excludeKinds*/ TypeFlags.Nullable); - type = unionTypes[id] = createObjectType(TypeFlags.Union | propagatedFlags); + type = setAndReturn(unionTypes, id, createObjectType(TypeFlags.Union | propagatedFlags)); type.types = typeSet; type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = aliasTypeArguments; @@ -5428,10 +5410,10 @@ namespace ts { return typeSet[0]; } const id = getTypeListId(typeSet); - let type = intersectionTypes[id]; + let type = intersectionTypes.get(id); if (!type) { const propagatedFlags = getPropagatingFlagsOfTypes(typeSet, /*excludeKinds*/ TypeFlags.Nullable); - type = intersectionTypes[id] = createObjectType(TypeFlags.Intersection | propagatedFlags); + type = setAndReturn(intersectionTypes, id, createObjectType(TypeFlags.Intersection | propagatedFlags)); type.types = typeSet; type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = aliasTypeArguments; @@ -5467,7 +5449,7 @@ namespace ts { function getLiteralTypeForText(flags: TypeFlags, text: string) { const map = flags & TypeFlags.StringLiteral ? stringLiteralTypes : numericLiteralTypes; - return hasProperty(map, text) ? map[text] : map[text] = createLiteralType(flags, text); + return getOrUpdate(map, text, () => createLiteralType(flags, text)); } function getTypeFromLiteralTypeNode(node: LiteralTypeNode): Type { @@ -6154,7 +6136,7 @@ namespace ts { return true; } - function isSimpleTypeRelatedTo(source: Type, target: Type, relation: Map, errorReporter?: ErrorReporter) { + function isSimpleTypeRelatedTo(source: Type, target: Type, relation: Relation, 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; @@ -6172,13 +6154,13 @@ namespace ts { return false; } - function isTypeRelatedTo(source: Type, target: Type, relation: Map) { + function isTypeRelatedTo(source: Type, target: Type, relation: Relation) { if (source === target || relation !== identityRelation && isSimpleTypeRelatedTo(source, target, relation)) { return true; } if (source.flags & TypeFlags.ObjectType && target.flags & TypeFlags.ObjectType) { const id = relation !== identityRelation || source.id < target.id ? source.id + "," + target.id : target.id + "," + source.id; - const related = relation[id]; + const related = relation.get(id); if (related !== undefined) { return related === RelationComparisonResult.Succeeded; } @@ -6202,7 +6184,7 @@ namespace ts { function checkTypeRelatedTo( source: Type, target: Type, - relation: Map, + relation: Relation, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: DiagnosticMessageChain): boolean { @@ -6210,7 +6192,7 @@ namespace ts { let errorInfo: DiagnosticMessageChain; let sourceStack: ObjectType[]; let targetStack: ObjectType[]; - let maybeStack: Map[]; + let maybeStack: Relation[]; let expandingFlags: number; let depth = 0; let overflow = false; @@ -6551,12 +6533,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; @@ -6565,7 +6547,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].has(id)) { return Ternary.Maybe; } } @@ -6582,8 +6564,7 @@ namespace ts { } sourceStack[depth] = source; targetStack[depth] = target; - maybeStack[depth] = {}; - maybeStack[depth][id] = RelationComparisonResult.Succeeded; + maybeStack[depth] = createStringMap(id, RelationComparisonResult.Succeeded); depth++; const saveExpandingFlags = expandingFlags; if (!(expandingFlags & 1) && isDeeplyNestedGeneric(source, sourceStack, depth)) expandingFlags |= 1; @@ -6613,12 +6594,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]; - copyMap(maybeCache, destinationCache); + copyStringMap(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; } @@ -6807,14 +6788,25 @@ namespace ts { return result; } - function eachPropertyRelatedTo(source: Type, target: Type, kind: IndexKind, reportErrors: boolean): Ternary { + 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; } @@ -7223,11 +7215,11 @@ namespace ts { } function transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) { - const members: SymbolTable = {}; + const members: SymbolTable = new StringMap(); for (const property of getPropertiesOfObjectType(type)) { const original = getTypeOfSymbol(property); const updated = f(original); - members[property.name] = updated === original ? property : createTransientSymbol(property, updated); + members.set(property.name, updated === original ? property : createTransientSymbol(property, updated)); }; return members; } @@ -7447,7 +7439,7 @@ namespace ts { let targetStack: Type[]; let depth = 0; let inferiority = 0; - const visited: Map = {}; + const visited = new StringSet(); inferFromTypes(source, target); function isInProcess(source: Type, target: Type) { @@ -7581,10 +7573,10 @@ namespace ts { return; } const key = source.id + "," + target.id; - if (hasProperty(visited, key)) { + if (visited.has(key)) { return; } - visited[key] = true; + visited.add(key); if (depth === 0) { sourceStack = []; targetStack = []; @@ -8335,12 +8327,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] = {}); + const cache = flowLoopCaches[id] || (flowLoopCaches[id] = new StringMap()); if (!key) { key = getFlowCacheKey(reference); } - if (cache[key]) { - return cache[key]; + const cached = cache.get(key); + if (cached) { + return cached; } // If this flow loop junction and reference are already being processed, return // the union of the types computed for each branch so far, marked as incomplete. @@ -8365,8 +8358,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); @@ -8384,7 +8378,7 @@ namespace ts { break; } } - return cache[key] = getUnionType(antecedentTypes, subtypeReduction); + return setAndReturn(cache, key, getUnionType(antecedentTypes, subtypeReduction)); } function isMatchingReferenceDiscriminant(expr: Expression) { @@ -8500,14 +8494,14 @@ namespace ts { // We narrow a non-union type to an exact primitive type if the non-union type // is a supertype of that primtive type. For example, type 'any' can be narrowed // to one of the primitive types. - const targetType = getProperty(typeofTypesByName, literal.text); + const targetType = typeofTypesByName.get(literal.text); if (targetType && isTypeSubtypeOf(targetType, type)) { return targetType; } } const facts = assumeTrue ? - getProperty(typeofEQFacts, literal.text) || TypeFacts.TypeofEQHostObject : - getProperty(typeofNEFacts, literal.text) || TypeFacts.TypeofNEHostObject; + typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject : + typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject; return getTypeWithFacts(type, facts); } @@ -9930,7 +9924,7 @@ namespace ts { // Grammar checking checkGrammarObjectLiteralExpression(node, inDestructuringPattern); - const propertiesTable: SymbolTable = {}; + const propertiesTable: SymbolTable = new StringMap(); const propertiesArray: Symbol[] = []; const contextualType = getApparentTypeOfContextualType(node); const contextualTypeHasPattern = contextualType && contextualType.pattern && @@ -10012,7 +10006,7 @@ namespace ts { } } else { - propertiesTable[member.name] = member; + propertiesTable.set(member.name, member); } propertiesArray.push(member); } @@ -10021,12 +10015,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 (!hasProperty(propertiesTable, prop.name)) { + if (!propertiesTable.has(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); } } @@ -10099,7 +10093,7 @@ namespace ts { } } - function checkJsxAttribute(node: JsxAttribute, elementAttributesType: Type, nameTable: Map) { + function checkJsxAttribute(node: JsxAttribute, elementAttributesType: Type, nameTable: StringSet) { let correspondingPropType: Type = undefined; // Look up the corresponding property for this attribute @@ -10139,34 +10133,32 @@ 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: StringSet) { 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 (!hasProperty(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; - } - return jsxTypes[name]; + return getOrUpdate(jsxTypes, name, () => + getExportedTypeFromNamespace(JsxNames.JSX, name) || unknownType); } /** @@ -10471,7 +10463,8 @@ namespace ts { const targetAttributesType = getJsxElementAttributesType(node); - const nameTable: Map = {}; + const nameTable = new StringSet(); + // Process this array in right-to-left order so we know which // attributes (mostly from spreads) are being overwritten and // thus should have their types ignored @@ -10494,9 +10487,7 @@ namespace ts { if (targetAttributesType && !sawSpreadedAny) { const targetProperties = getPropertiesOfType(targetAttributesType); for (let i = 0; i < targetProperties.length; i++) { - if (!(targetProperties[i].flags & SymbolFlags.Optional) && - !hasProperty(nameTable, targetProperties[i].name)) { - + if (!(targetProperties[i].flags & SymbolFlags.Optional) && !nameTable.has(targetProperties[i].name)) { error(node, Diagnostics.Property_0_is_missing_in_type_1, targetProperties[i].name, typeToString(targetAttributesType)); } } @@ -11209,7 +11200,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: Relation, 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 @@ -11743,7 +11734,7 @@ namespace ts { diagnostics.add(createDiagnosticForNodeFromMessageChain(node, errorInfo)); } - function chooseOverload(candidates: Signature[], relation: Map, signatureHelpTrailingComma = false) { + function chooseOverload(candidates: Signature[], relation: Relation, signatureHelpTrailingComma = false) { for (const originalCandidate of candidates) { if (!hasCorrectArity(node, args, originalCandidate, signatureHelpTrailingComma)) { continue; @@ -13779,8 +13770,8 @@ namespace ts { function checkClassForDuplicateDeclarations(node: ClassLikeDeclaration) { const getter = 1, setter = 2, property = getter | setter; - const instanceNames: Map = {}; - const staticNames: Map = {}; + const instanceNames = new StringMap(); + const staticNames = new StringMap(); for (const member of node.members) { if (member.kind === SyntaxKind.Constructor) { for (const param of (member as ConstructorDeclaration).parameters) { @@ -13812,24 +13803,24 @@ namespace ts { } } - function addName(names: Map, location: Node, name: string, meaning: number) { - if (hasProperty(names, name)) { - const prev = names[name]; + function addName(names: StringMap, location: Node, name: string, meaning: number) { + const prev = names.get(name); + if (prev !== undefined) { 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: Map = {}; + const names = new StringSet(); for (const member of node.members) { if (member.kind == SyntaxKind.PropertySignature) { let memberName: string; @@ -13843,12 +13834,12 @@ namespace ts { continue; } - if (hasProperty(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); } } } @@ -15054,25 +15045,22 @@ namespace ts { function checkUnusedLocalsAndParameters(node: Node): void { if (node.parent.kind !== SyntaxKind.InterfaceDeclaration && noUnusedIdentifiers && !isInAmbientContext(node)) { - for (const key in node.locals) { - if (hasProperty(node.locals, key)) { - const local = node.locals[key]; - if (!local.isReferenced) { - if (local.valueDeclaration && local.valueDeclaration.kind === SyntaxKind.Parameter) { - const parameter = local.valueDeclaration; - if (compilerOptions.noUnusedParameters && - !isParameterPropertyDeclaration(parameter) && - !parameterIsThisKeyword(parameter) && - !parameterNameStartsWithUnderscore(parameter)) { - error(local.valueDeclaration.name, Diagnostics._0_is_declared_but_never_used, local.name); - } - } - else if (compilerOptions.noUnusedLocals) { - forEach(local.declarations, d => error(d.name || d, Diagnostics._0_is_declared_but_never_used, local.name)); + node.locals.forEach(local => { + if (!local.isReferenced) { + if (local.valueDeclaration && local.valueDeclaration.kind === SyntaxKind.Parameter) { + const parameter = local.valueDeclaration; + if (compilerOptions.noUnusedParameters && + !isParameterPropertyDeclaration(parameter) && + !parameterIsThisKeyword(parameter) && + !parameterNameStartsWithUnderscore(parameter)) { + error(local.valueDeclaration.name, Diagnostics._0_is_declared_but_never_used, local.name); } } + else if (compilerOptions.noUnusedLocals) { + forEach(local.declarations, d => error(d.name || d, Diagnostics._0_is_declared_but_never_used, local.name)); + } } - } + }); } } @@ -15126,18 +15114,15 @@ namespace ts { function checkUnusedModuleMembers(node: ModuleDeclaration | SourceFile): void { if (compilerOptions.noUnusedLocals && !isInAmbientContext(node)) { - for (const key in node.locals) { - if (hasProperty(node.locals, key)) { - const local = node.locals[key]; - if (!local.isReferenced && !local.exportSymbol) { - for (const declaration of local.declarations) { - if (!isAmbientModule(declaration)) { - error(declaration.name, Diagnostics._0_is_declared_but_never_used, local.name); - } + node.locals.forEach(local => { + if (!local.isReferenced && !local.exportSymbol) { + for (const declaration of local.declarations) { + if (!isAmbientModule(declaration)) { + error(declaration.name, Diagnostics._0_is_declared_but_never_used, local.name); } } } - } + }); } } @@ -16142,8 +16127,8 @@ namespace ts { else { const identifierName = (catchClause.variableDeclaration.name).text; const locals = catchClause.block.locals; - if (locals && hasProperty(locals, identifierName)) { - const localSymbol = locals[identifierName]; + if (locals) { + const localSymbol = locals.get(identifierName); if (localSymbol && (localSymbol.flags & SymbolFlags.BlockScopedVariable) !== 0) { grammarErrorOnNode(localSymbol.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, identifierName); } @@ -16563,18 +16548,18 @@ namespace ts { return true; } - const seen: Map<{ prop: Symbol; containingType: Type }> = {}; - forEach(resolveDeclaredMembers(type).declaredProperties, p => { seen[p.name] = { prop: p, containingType: type }; }); + const seen = new StringMap<{ prop: Symbol; containingType: Type }>(); + forEach(resolveDeclaredMembers(type).declaredProperties, p => { seen.set(p.name, { prop: p, containingType: type }); }); let ok = true; for (const base of baseTypes) { const properties = getPropertiesOfObjectType(getTypeWithThisArgument(base, type.thisType)); for (const prop of properties) { - if (!hasProperty(seen, prop.name)) { - seen[prop.name] = { prop: prop, containingType: base }; + const existing = seen.get(prop.name); + if (!existing) { + seen.set(prop.name, { prop: prop, containingType: base }); } else { - const existing = seen[prop.name]; const isInheritedProperty = existing.containingType !== type; if (isInheritedProperty && !isPropertyIdenticalTo(existing.prop, prop)) { ok = false; @@ -17312,19 +17297,14 @@ namespace ts { } function hasExportedMembers(moduleSymbol: Symbol) { - for (const id in moduleSymbol.exports) { - if (id !== "export=") { - return true; - } - } - return false; + return someInStringMap(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 = getExportAssignmentSymbol(moduleSymbol); if (exportEqualsSymbol && hasExportedMembers(moduleSymbol)) { const declaration = getDeclarationOfAliasSymbol(exportEqualsSymbol) || exportEqualsSymbol.valueDeclaration; if (!isTopLevelInExternalModuleAugmentation(declaration)) { @@ -17333,21 +17313,20 @@ namespace ts { } // Checks for export * conflicts const exports = getExportsOfModule(moduleSymbol); - for (const id in exports) { + exports.forEach(({declarations, flags}, id) => { if (id === "__export") { - continue; + return; } - const { declarations, flags } = exports[id]; // ECMA262: 15.2.1.1 It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries. // (TS Exceptions: namespaces, function overloads, enums, and interfaces) if (flags & (SymbolFlags.Namespace | SymbolFlags.Interface | SymbolFlags.Enum)) { - continue; + return; } const exportedDeclarationsCount = countWhere(declarations, isNotOverload); if (flags & SymbolFlags.TypeAlias && exportedDeclarationsCount <= 2) { // it is legal to merge type alias with other values // so count should be either 1 (just type alias) or 2 (type alias + merged value) - continue; + return; } if (exportedDeclarationsCount > 1) { for (const declaration of declarations) { @@ -17356,7 +17335,7 @@ namespace ts { } } } - } + }); links.exportsChecked = true; } @@ -17632,7 +17611,7 @@ namespace ts { } function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] { - const symbols: SymbolTable = {}; + const symbols: SymbolTable = new StringMap(); let memberFlags: NodeFlags = 0; if (isInsideWithStatementBody(location)) { @@ -17706,22 +17685,16 @@ namespace ts { */ function copySymbol(symbol: Symbol, meaning: SymbolFlags): void { if (symbol.flags & meaning) { - const id = symbol.name; // We will copy all symbol regardless of its reserved name because // symbolsToArray will check whether the key is a reserved name and // it will not copy symbol with reserved name to the array - if (!hasProperty(symbols, id)) { - symbols[id] = symbol; - } + setButDontOverride(symbols, symbol.name, () => symbol); } } function copySymbols(source: SymbolTable, meaning: SymbolFlags): void { if (meaning) { - for (const id in source) { - const symbol = source[id]; - copySymbol(symbol, meaning); - } + source.forEach(symbol => copySymbol(symbol, meaning)); } } } @@ -18130,11 +18103,9 @@ namespace ts { type = getApparentType(type); const propsByName = createSymbolTable(getPropertiesOfType(type)); if (getSignaturesOfType(type, SignatureKind.Call).length || getSignaturesOfType(type, SignatureKind.Construct).length) { - forEach(getPropertiesOfType(globalFunctionType), p => { - if (!hasProperty(propsByName, p.name)) { - propsByName[p.name] = p; - } - }); + for (const p of getPropertiesOfType(globalFunctionType)) { + setButDontOverride(propsByName, p.name, () => p); + } } return getNamedMembers(propsByName); } @@ -18188,7 +18159,7 @@ namespace ts { // otherwise - check if at least one export is value symbolLinks.exportsSomeValue = hasExportAssignment ? !!(moduleSymbol.flags & SymbolFlags.Value) - : forEachValue(getExportsOfModule(moduleSymbol), isValue); + : someInStringMap(getExportsOfModule(moduleSymbol), isValue); } return symbolLinks.exportsSomeValue; @@ -18479,7 +18450,7 @@ namespace ts { } function hasGlobalName(name: string): boolean { - return hasProperty(globals, name); + return globals.has(name); } function getReferencedValueSymbol(reference: Identifier): Symbol { @@ -18502,17 +18473,12 @@ 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) { - if (!hasProperty(resolvedTypeReferenceDirectives, key)) { - continue; + resolvedTypeReferenceDirectives.forEach((resolvedDirective, key) => { + if (resolvedDirective) { + const file = host.getSourceFile(resolvedDirective.resolvedFileName); + fileToDirective.set(file.path, key); } - const resolvedDirective = resolvedTypeReferenceDirectives[key]; - if (!resolvedDirective) { - continue; - } - const file = host.getSourceFile(resolvedDirective.resolvedFileName); - fileToDirective.set(file.path, key); - } + }); } return { getReferencedExportContainer, @@ -19294,7 +19260,7 @@ namespace ts { } function checkGrammarObjectLiteralExpression(node: ObjectLiteralExpression, inDestructuring: boolean) { - const seen: Map = {}; + const seen = new StringMap(); const Property = 1; const GetAccessor = 2; const SetAccessor = 4; @@ -19356,17 +19322,17 @@ namespace ts { continue; } - if (!hasProperty(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); @@ -19380,7 +19346,7 @@ namespace ts { } function checkGrammarJsxElement(node: JsxOpeningLikeElement) { - const seen: Map = {}; + const seen = new StringSet(); for (const attr of node.attributes) { if (attr.kind === SyntaxKind.JsxSpreadAttribute) { continue; @@ -19388,8 +19354,8 @@ namespace ts { const jsxAttr = (attr); const name = jsxAttr.name; - if (!hasProperty(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); diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 7e2c6eb8d334e..7bc5ac8daeff4 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -61,10 +61,10 @@ namespace ts { }, { name: "jsx", - type: { - "preserve": JsxEmit.Preserve, - "react": JsxEmit.React - }, + type: new StringMap([ + ["preserve", JsxEmit.Preserve], + ["react", JsxEmit.React] + ]), paramType: Diagnostics.KIND, description: Diagnostics.Specify_JSX_code_generation_Colon_preserve_or_react, }, @@ -91,24 +91,24 @@ namespace ts { { name: "module", shortName: "m", - type: { - "none": ModuleKind.None, - "commonjs": ModuleKind.CommonJS, - "amd": ModuleKind.AMD, - "system": ModuleKind.System, - "umd": ModuleKind.UMD, - "es6": ModuleKind.ES6, - "es2015": ModuleKind.ES2015, - }, + type: new StringMap([ + ["none", ModuleKind.None], + ["commonjs", ModuleKind.CommonJS], + ["amd", ModuleKind.AMD], + ["system", ModuleKind.System], + ["umd", ModuleKind.UMD], + ["es6", ModuleKind.ES6], + ["es2015", ModuleKind.ES2015], + ]), description: Diagnostics.Specify_module_code_generation_Colon_commonjs_amd_system_umd_or_es2015, paramType: Diagnostics.KIND, }, { name: "newLine", - type: { - "crlf": NewLineKind.CarriageReturnLineFeed, - "lf": NewLineKind.LineFeed - }, + type: new StringMap([ + ["crlf", NewLineKind.CarriageReturnLineFeed], + ["lf", NewLineKind.LineFeed] + ]), description: Diagnostics.Specify_the_end_of_line_sequence_to_be_used_when_emitting_files_Colon_CRLF_dos_or_LF_unix, paramType: Diagnostics.NEWLINE, }, @@ -250,12 +250,12 @@ namespace ts { { name: "target", shortName: "t", - type: { - "es3": ScriptTarget.ES3, - "es5": ScriptTarget.ES5, - "es6": ScriptTarget.ES6, - "es2015": ScriptTarget.ES2015, - }, + type: new StringMap([ + ["es3", ScriptTarget.ES3], + ["es5", ScriptTarget.ES5], + ["es6", ScriptTarget.ES6], + ["es2015", ScriptTarget.ES2015], + ]), description: Diagnostics.Specify_ECMAScript_target_version_Colon_ES3_default_ES5_or_ES2015, paramType: Diagnostics.VERSION, }, @@ -284,10 +284,10 @@ namespace ts { }, { name: "moduleResolution", - type: { - "node": ModuleResolutionKind.NodeJs, - "classic": ModuleResolutionKind.Classic, - }, + type: new StringMap([ + ["node", ModuleResolutionKind.NodeJs], + ["classic", ModuleResolutionKind.Classic], + ]), description: Diagnostics.Specify_module_resolution_strategy_Colon_node_Node_js_or_classic_TypeScript_pre_1_6, }, { @@ -392,32 +392,32 @@ namespace ts { type: "list", element: { name: "lib", - type: { + type: new StringMap([ // JavaScript only - "es5": "lib.es5.d.ts", - "es6": "lib.es2015.d.ts", - "es2015": "lib.es2015.d.ts", - "es7": "lib.es2016.d.ts", - "es2016": "lib.es2016.d.ts", - "es2017": "lib.es2017.d.ts", + ["es5", "lib.es5.d.ts"], + ["es6", "lib.es2015.d.ts"], + ["es2015", "lib.es2015.d.ts"], + ["es7", "lib.es2016.d.ts"], + ["es2016", "lib.es2016.d.ts"], + ["es2017", "lib.es2017.d.ts"], // Host only - "dom": "lib.dom.d.ts", - "webworker": "lib.webworker.d.ts", - "scripthost": "lib.scripthost.d.ts", + ["dom", "lib.dom.d.ts"], + ["webworker", "lib.webworker.d.ts"], + ["scripthost", "lib.scripthost.d.ts"], // ES2015 Or ESNext By-feature options - "es2015.core": "lib.es2015.core.d.ts", - "es2015.collection": "lib.es2015.collection.d.ts", - "es2015.generator": "lib.es2015.generator.d.ts", - "es2015.iterable": "lib.es2015.iterable.d.ts", - "es2015.promise": "lib.es2015.promise.d.ts", - "es2015.proxy": "lib.es2015.proxy.d.ts", - "es2015.reflect": "lib.es2015.reflect.d.ts", - "es2015.symbol": "lib.es2015.symbol.d.ts", - "es2015.symbol.wellknown": "lib.es2015.symbol.wellknown.d.ts", - "es2016.array.include": "lib.es2016.array.include.d.ts", - "es2017.object": "lib.es2017.object.d.ts", - "es2017.sharedmemory": "lib.es2017.sharedmemory.d.ts" - }, + ["es2015.core", "lib.es2015.core.d.ts"], + ["es2015.collection", "lib.es2015.collection.d.ts"], + ["es2015.generator", "lib.es2015.generator.d.ts"], + ["es2015.iterable", "lib.es2015.iterable.d.ts"], + ["es2015.promise", "lib.es2015.promise.d.ts"], + ["es2015.proxy", "lib.es2015.proxy.d.ts"], + ["es2015.reflect", "lib.es2015.reflect.d.ts"], + ["es2015.symbol", "lib.es2015.symbol.d.ts"], + ["es2015.symbol.wellknown", "lib.es2015.symbol.wellknown.d.ts"], + ["es2016.array.include", "lib.es2016.array.include.d.ts"], + ["es2017.object", "lib.es2017.object.d.ts"], + ["es2017.sharedmemory", "lib.es2017.sharedmemory.d.ts"] + ]), }, description: Diagnostics.Specify_library_files_to_be_included_in_the_compilation_Colon }, @@ -458,8 +458,8 @@ namespace ts { /* @internal */ export interface OptionNameMap { - optionNameMap: Map; - shortOptionNames: Map; + optionNameMap: StringMap; + shortOptionNames: StringMap; } let optionNameMapCache: OptionNameMap; @@ -470,12 +470,12 @@ namespace ts { return optionNameMapCache; } - const optionNameMap: Map = {}; - const shortOptionNames: Map = {}; + const optionNameMap = new StringMap(); + const shortOptionNames = new StringMap(); forEach(optionDeclarations, option => { - optionNameMap[option.name.toLowerCase()] = option; + optionNameMap.set(option.name.toLowerCase(), option); if (option.shortName) { - shortOptionNames[option.shortName] = option.name; + shortOptionNames.set(option.shortName, option.name); } }); @@ -486,7 +486,7 @@ namespace ts { /* @internal */ export function createCompilerDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType): Diagnostic { const namesOfType: string[] = []; - forEachKey(opt.type, key => { + forEachKeyInStringMap(opt.type, key => { namesOfType.push(` '${key}'`); }); @@ -496,9 +496,9 @@ namespace ts { /* @internal */ export function parseCustomTypeOption(opt: CommandLineOptionOfCustomType, value: string, errors: Diagnostic[]) { const key = trimString((value || "")).toLowerCase(); - const map = opt.type; - if (hasProperty(map, key)) { - return map[key]; + const mapped = opt.type.get(key); + if (mapped !== undefined) { + return mapped; } else { errors.push(createCompilerDiagnosticForInvalidCustomType(opt)); @@ -551,13 +551,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 (hasProperty(shortOptionNames, s)) { - s = shortOptionNames[s]; + const short = shortOptionNames.get(s); + if (short) { + s = short; } - if (hasProperty(optionNameMap, s)) { - const opt = optionNameMap[s]; - + const opt = optionNameMap.get(s); + if (opt) { if (opt.isTSConfigOnly) { errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file, opt.name)); } @@ -808,11 +808,11 @@ namespace ts { return; } - const optionNameMap = arrayToMap(optionDeclarations, opt => opt.name); + const optionNameMap = createStringMapFromValues(optionDeclarations, opt => opt.name); for (const id in jsonOptions) { - if (hasProperty(optionNameMap, id)) { - const opt = optionNameMap[id]; + const opt = optionNameMap.get(id); + if (opt) { defaultOptions[opt.name] = convertJsonOption(opt, jsonOptions[id], basePath, errors); } else { @@ -848,8 +848,9 @@ namespace ts { function convertJsonOptionOfCustomType(opt: CommandLineOptionOfCustomType, value: string, errors: Diagnostic[]) { const key = value.toLowerCase(); - if (hasProperty(opt.type, key)) { - return opt.type[key]; + const mapped = opt.type.get(key); + if (mapped) { + return mapped; } else { errors.push(createCompilerDiagnosticForInvalidCustomType(opt)); @@ -958,12 +959,12 @@ namespace ts { // Literal file names (provided via the "files" array in tsconfig.json) are stored in a // file map with a possibly case insensitive key. We use this map later when when including // wildcard paths. - const literalFileMap: Map = {}; + const literalFileMap = new StringMap(); // Wildcard paths (provided via the "includes" array in tsconfig.json) are stored in a // file map with a possibly case insensitive key. We use this map to store paths matched // via wildcard, and to handle extension priority. - const wildcardFileMap: Map = {}; + const wildcardFileMap = new StringMap(); if (include) { include = validateSpecs(include, errors, /*allowTrailingRecursion*/ false); @@ -977,7 +978,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 = 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. @@ -988,7 +989,7 @@ namespace ts { if (fileNames) { for (const fileName of fileNames) { const file = combinePaths(basePath, fileName); - literalFileMap[keyMapper(file)] = file; + literalFileMap.set(keyMapper(file), file); } } @@ -1011,14 +1012,14 @@ namespace ts { removeWildcardFilesWithLowerPriorityExtension(file, wildcardFileMap, supportedExtensions, keyMapper); const key = keyMapper(file); - if (!hasProperty(literalFileMap, key) && !hasProperty(wildcardFileMap, key)) { - wildcardFileMap[key] = file; + if (!literalFileMap.has(key)) { + setButDontOverride(wildcardFileMap, key, () => file); } } } - const literalFiles = reduceProperties(literalFileMap, addFileToOutput, []); - const wildcardFiles = reduceProperties(wildcardFileMap, addFileToOutput, []); + const literalFiles = valuesArray(literalFileMap); + const wildcardFiles = valuesArray(wildcardFileMap); wildcardFiles.sort(host.useCaseSensitiveFileNames ? compareStrings : compareStringsCaseInsensitive); return { fileNames: literalFiles.concat(wildcardFiles), @@ -1049,7 +1050,7 @@ namespace ts { /** * Gets directories in a set of include patterns that should be watched for changes. */ - function getWildcardDirectories(include: string[], exclude: string[], path: string, useCaseSensitiveFileNames: boolean) { + function getWildcardDirectories(include: string[], exclude: string[], path: string, useCaseSensitiveFileNames: boolean): Map { // We watch a directory recursively if it contains a wildcard anywhere in a directory segment // of the pattern: // @@ -1087,15 +1088,13 @@ namespace ts { } // Remove any subpaths under an existing recursively watched directory. - for (const key in wildcardDirectories) { - if (hasProperty(wildcardDirectories, key)) { - for (const recursiveKey of recursiveKeys) { - if (key !== recursiveKey && containsPath(recursiveKey, key, path, !useCaseSensitiveFileNames)) { - delete wildcardDirectories[key]; - } + ts.forEachKey(wildcardDirectories, key => { + for (const recursiveKey of recursiveKeys) { + if (key !== recursiveKey && containsPath(recursiveKey, key, path, !useCaseSensitiveFileNames)) { + delete wildcardDirectories[key]; } } - } + }); } return wildcardDirectories; @@ -1109,13 +1108,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: StringMap, wildcardFiles: StringMap, 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 (hasProperty(literalFiles, higherPriorityPath) || hasProperty(wildcardFiles, higherPriorityPath)) { + if (literalFiles.has(higherPriorityPath) || wildcardFiles.has(higherPriorityPath)) { return true; } } @@ -1131,26 +1130,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: StringMap, 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 6c87ad82955c3..07bc4342a68be 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -20,7 +20,7 @@ namespace ts { } export function createFileMap(keyMapper?: (key: string) => string): FileMap { - let files: Map = {}; + const files = new StringMap(); return { get, set, @@ -31,31 +31,28 @@ 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, value)); } // 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(path); } function set(path: Path, value: T) { - files[toKey(path)] = value; + files.set(toKey(path), value); } function contains(path: Path) { - return hasProperty(files, toKey(path)); + return files.has(toKey(path)); } function remove(path: Path) { - const key = toKey(path); - delete files[key]; + files.delete(toKey(path)); } function clear() { - files = {}; + files.clear(); } function toKey(path: Path): string { @@ -76,45 +73,6 @@ namespace ts { GreaterThan = 1 } - /** - * Iterates through 'array' by index and performs the callback on each element of array until the callback - * returns a truthy value, then returns that value. - * If no such value is found, the callback is applied to each element of array and undefined is returned. - */ - export function forEach(array: T[], callback: (element: T, index: number) => U): U { - if (array) { - for (let i = 0, len = array.length; i < len; i++) { - const result = callback(array[i], i); - if (result) { - return result; - } - } - } - return undefined; - } - - export function contains(array: T[], value: T): boolean { - if (array) { - for (const v of array) { - if (v === value) { - return true; - } - } - } - return false; - } - - export function indexOf(array: T[], value: T): number { - if (array) { - for (let i = 0, len = array.length; i < len; i++) { - if (array[i] === value) { - return i; - } - } - } - return -1; - } - export function indexOfAnyCharCode(text: string, charCodes: number[], start?: number): number { for (let i = start || 0, len = text.length; i < len; i++) { if (contains(charCodes, text.charCodeAt(i))) { @@ -124,316 +82,6 @@ namespace ts { return -1; } - export function countWhere(array: T[], predicate: (x: T) => boolean): number { - let count = 0; - if (array) { - for (const v of array) { - if (predicate(v)) { - count++; - } - } - } - return count; - } - - export function filter(array: T[], f: (x: T) => boolean): T[] { - let result: T[]; - if (array) { - result = []; - for (const item of array) { - if (f(item)) { - result.push(item); - } - } - } - return result; - } - - export function filterMutate(array: T[], f: (x: T) => boolean): void { - let outIndex = 0; - for (const item of array) { - if (f(item)) { - array[outIndex] = item; - outIndex++; - } - } - array.length = outIndex; - } - - export function map(array: T[], f: (x: T) => U): U[] { - let result: U[]; - if (array) { - result = []; - for (const v of array) { - result.push(f(v)); - } - } - return result; - } - - export function concatenate(array1: T[], array2: T[]): T[] { - if (!array2 || !array2.length) return array1; - if (!array1 || !array1.length) return array2; - - return array1.concat(array2); - } - - export function deduplicate(array: T[], areEqual?: (a: T, b: T) => boolean): T[] { - let result: T[]; - if (array) { - result = []; - loop: for (const item of array) { - for (const res of result) { - if (areEqual ? areEqual(res, item) : res === item) { - continue loop; - } - } - result.push(item); - } - } - return result; - } - - export function sum(array: any[], prop: string): number { - let result = 0; - for (const v of array) { - result += v[prop]; - } - return result; - } - - export function addRange(to: T[], from: T[]): void { - if (to && from) { - for (const v of from) { - to.push(v); - } - } - } - - export function rangeEquals(array1: T[], array2: T[], pos: number, end: number) { - while (pos < end) { - if (array1[pos] !== array2[pos]) { - return false; - } - pos++; - } - return true; - } - - /** - * Returns the last element of an array if non-empty, undefined otherwise. - */ - export function lastOrUndefined(array: T[]): T { - if (array.length === 0) { - return undefined; - } - - return array[array.length - 1]; - } - - /** - * Performs a binary search, finding the index at which 'value' occurs in 'array'. - * If no such index is found, returns the 2's-complement of first index at which - * number[index] exceeds number. - * @param array A sorted array whose first element must be no larger than number - * @param number The value to be searched for in the array. - */ - export function binarySearch(array: number[], value: number): number { - let low = 0; - let high = array.length - 1; - - while (low <= high) { - const middle = low + ((high - low) >> 1); - const midValue = array[middle]; - - if (midValue === value) { - return middle; - } - else if (midValue > value) { - high = middle - 1; - } - else { - low = middle + 1; - } - } - - return ~low; - } - - export function reduceLeft(array: T[], f: (a: T, x: T) => T): T; - export function reduceLeft(array: T[], f: (a: U, x: T) => U, initial: U): U; - export function reduceLeft(array: T[], f: (a: U, x: T) => U, initial?: U): U { - if (array) { - const count = array.length; - if (count > 0) { - let pos = 0; - let result: T | U; - if (arguments.length <= 2) { - result = array[pos]; - pos++; - } - else { - result = initial; - } - while (pos < count) { - result = f(result, array[pos]); - pos++; - } - return result; - } - } - return initial; - } - - export function reduceRight(array: T[], f: (a: T, x: T) => T): T; - export function reduceRight(array: T[], f: (a: U, x: T) => U, initial: U): U; - export function reduceRight(array: T[], f: (a: U, x: T) => U, initial?: U): U { - if (array) { - let pos = array.length - 1; - if (pos >= 0) { - let result: T | U; - if (arguments.length <= 2) { - result = array[pos]; - pos--; - } - else { - result = initial; - } - while (pos >= 0) { - result = f(result, array[pos]); - pos--; - } - return result; - } - } - return initial; - } - - const hasOwnProperty = Object.prototype.hasOwnProperty; - - export function hasProperty(map: Map, key: string): boolean { - return hasOwnProperty.call(map, key); - } - - export function getKeys(map: Map): string[] { - const keys: string[] = []; - for (const key in map) { - keys.push(key); - } - return keys; - } - - export function getProperty(map: Map, key: string): T | undefined { - return hasProperty(map, key) ? map[key] : undefined; - } - - export function getOrUpdateProperty(map: Map, key: string, makeValue: () => T): T { - return hasProperty(map, key) ? map[key] : map[key] = makeValue(); - } - - export function isEmpty(map: Map) { - for (const id in map) { - if (hasProperty(map, id)) { - return false; - } - } - return true; - } - - export function clone(object: T): T { - const result: any = {}; - for (const id in object) { - result[id] = (object)[id]; - } - return result; - } - - export function extend, T2 extends Map<{}>>(first: T1 , second: T2): T1 & T2 { - const result: T1 & T2 = {}; - for (const id in first) { - (result as any)[id] = first[id]; - } - for (const id in second) { - if (!hasProperty(result, id)) { - (result as any)[id] = second[id]; - } - } - return result; - } - - export function forEachValue(map: Map, callback: (value: T) => U): U { - let result: U; - for (const id in map) { - if (result = callback(map[id])) break; - } - return result; - } - - export function forEachKey(map: Map, callback: (key: string) => U): U { - let result: U; - for (const id in map) { - if (result = callback(id)) break; - } - return result; - } - - export function lookUp(map: Map, key: string): T { - return hasProperty(map, key) ? map[key] : undefined; - } - - export function copyMap(source: Map, target: Map): void { - for (const p in source) { - target[p] = source[p]; - } - } - - /** - * 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 { - const result: Map = {}; - - forEach(array, value => { - result[makeKey(value)] = value; - }); - - return result; - } - - /** - * Reduce the properties of a map. - * - * @param map The map to reduce - * @param callback An aggregation function that is called for each entry in the map - * @param initial The initial value for the reduction. - */ - export function reduceProperties(map: Map, callback: (aggregate: U, value: T, key: string) => U, initial: U): U { - let result = initial; - if (map) { - for (const key in map) { - if (hasProperty(map, key)) { - result = callback(result, map[key], String(key)); - } - } - } - - return result; - } - - /** - * Tests whether a value is an array. - */ - export function isArray(value: any): value is any[] { - return Array.isArray ? Array.isArray(value) : value instanceof Array; - } - export function memoize(callback: () => T): () => T { let value: T; return () => { diff --git a/src/compiler/dataStructures.ts b/src/compiler/dataStructures.ts new file mode 100644 index 0000000000000..8d9df05950e21 --- /dev/null +++ b/src/compiler/dataStructures.ts @@ -0,0 +1,802 @@ +// StringMap and NumberMap +namespace ts { + export interface MapCommon { + /** Removes all entries. */ + clear(): void; + /** Removes the entry with the given key. */ + delete(key: K): void; + /** Gets the value associated with a key. */ + get(key: K): V | undefined; + /** Whether any entry in the map has the given key. */ + has(key: K): boolean; + /** Associate a value with a key. */ + set(key: K, value: V): void; + } + + /** String-keyed Map. */ + export interface StringMap extends MapCommon { + /** + * Iterate over all entries in the map + * For halting iteration, see `findInMap`. + */ + forEach(fn: (value: V, key: string) => void): void; + } + export interface StringMapConstructor { + new(): StringMap; + /** Map whose entries are the given [key, value] pairs. */ + new(entries?: [string, V][]): StringMap; + /** Clone another map. */ + new(otherMap: StringMap): StringMap; + } + + /** Number-keyed Map. */ + export interface NumberMap extends MapCommon {} + export interface NumberMapConstructor { + new(): NumberMap; + new(entries?: [number, V][]): NumberMap; + } + + abstract class ShimMapCommon implements MapCommon { + protected data: any; // Map | V[] + + abstract clear(): void; + + delete(key: K) { + delete this.data[key]; + } + + get(key: K) { + return this.has(key) ? this.data[key] : undefined; + } + + has(key: K) { + return hasProperty(this.data, key); + } + + set(key: K, value: V) { + this.data[key] = value; + } + } + + /** Implementation of Map for JS engines that don't natively support it. */ + class ShimStringMap extends ShimMapCommon implements StringMap { + data: Map; + + constructor(entries?: [string, V][]); + constructor(otherMap: StringMap); + constructor(argument?: any) { + super(); + this.data = {}; + + if (argument !== undefined) { + if (argument instanceof ShimStringMap) { + this.data = clone(argument.data); + } + else { + Debug.assert(argument instanceof Array); + for (const [key, value] of argument) { + this.set(key, value); + } + } + } + } + + clear() { + this.data = {}; + } + + forEach(fn: (value: V, index: string) => void) { + for (const key in this.data) { + if (this.has(key)) { + fn(this.data[key], key); + } + } + } + + // Support this directly instead of emulating an iterator, which is slow. + find(fn: (value: V, key: string) => U | undefined): U | undefined { + for (const key in this.data) { + if (this.has(key)) { + const result = fn(this.data[key], key); + if (result) { + return result; + } + } + } + } + } + + /** Implementation of Map for JS engines that don't natively support it. */ + class ShimNumberMap extends ShimMapCommon implements NumberMap { + data: V[]; + + constructor(entries?: [number, V][]) { + super(); + this.data = []; + if (entries) { + for (const [key, value] of entries) { + this.set(key, value); + } + } + } + + clear() { + this.data = []; + } + } + + // This is the globally available native Map class. It's unrelated to the ts.Map type. + declare const Map: (StringMapConstructor & NumberMapConstructor) | undefined; + export const StringMap: StringMapConstructor = typeof Map === "undefined" ? ShimStringMap : Map; + export const NumberMap: NumberMapConstructor = typeof Map === "undefined" ? ShimNumberMap : Map; + + /** Number of (key, value) pairs in a map. */ + export function stringMapSize(map: StringMap): number { + if (map instanceof ShimStringMap) { + let size = 0; + map.forEach(() => { size++; }); + return size; + } + else { + // For native maps, this is available as a property. + return (map).size; + } + } + + /** Iterate through a map, returning the first truthy value returned by `fn`, or undefined. */ + export function findInStringMap(map: StringMap, fn: (value: V, key: string) => U | undefined): U | undefined { + if (map instanceof ShimStringMap) { + return map.find(fn); + } + else { + // Using an iterator and testing for `done` performs better than using forEach() and throwing an exception. + const iterator: { next(): { value: [string, V], done: boolean } } = (map).entries(); + while (true) { + const { value: pair, done } = iterator.next(); + if (done) { + return undefined; + } + const [key, value] = pair; + const result = fn(value, key); + if (result) { + return result; + } + } + } + } + + /** + * When using an Object to shim a String Map, the iteration order of the keys is implementation-dependent. + * V8 moves natural-number-like keys to the front, then sorts, so we emulate that behavior here. + * (See https://bugs.chromium.org/p/v8/issues/detail?id=164) + * Other runtimes always use insertion order. + * If we have values taken from a Map or an Object fully respecting insertion order, this will convert it to the V8 order. + * If we have values taken from a V8 Object, this should effectively do nothing. + */ + export function sortInV8ObjectInsertionOrder(values: T[], toKey: (t: T) => string): T[] { + const naturals: T[] = []; + const everythingElse: 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 ? naturals : everythingElse).push(value); + } + function toInt(value: T): number { + return parseInt(toKey(value), 10); + } + naturals.sort((a, b) => toInt(a) - toInt(b)); + return naturals.concat(everythingElse); + } + + /** Iterate over every key. */ + export function forEachKeyInStringMap(map: StringMap, callback: (key: string) => void): void { + map.forEach((_, key) => callback(key)); + } + + /** Copy all entries from `source` to `target`, overwriting any already existing. */ + export function copyStringMap(source: StringMap, target: StringMap): void { + source.forEach((value, key) => { + target.set(key, value); + }); + } + + /** Array of all keys in a map. */ + export function keysArray(map: StringMap): string[] { + const keys: string[] = []; + forEachKeyInStringMap(map, key => keys.push(key)); + return keys; + } + + /** Array of all values in a map. */ + export function valuesArray(map: StringMap): V[] { + const values: V[] = []; + map.forEach(value => values.push(value)); + return values; + } + + /** Set `map.get(key) === value`, and return `value`. */ + export function setAndReturn(map: MapCommon, key: K, value: V): V { + map.set(key, value); + return value; + } + + /** Like `getOrUpdateMap` but does not return a value. */ + export function setButDontOverride(map: MapCommon, key: K, getValue: () => V): void { + if (!map.has(key)) { + map.set(key, getValue()); + } + } + + /** Sets `map.get(key) === getValue()` unless `map.get(key)` was already defined. */ + export function getOrUpdate(map: MapCommon, key: K, getValue: () => V): V { + const value = map.get(key); + if (value === undefined) { + const value = getValue(); + map.set(key, value); + return value; + } + else { + return value; + } + } + + /** True if the predicate is true for some entry in the map. */ + export function someInStringMap(map: StringMap, predicate: (value: V, key: string) => boolean): boolean { + return !!findInStringMap(map, predicate); + } + + /** True if the predicate is true for every entry in the map. */ + export function allInStringMap(map: StringMap, predicate: (value: V, key: string) => boolean): boolean { + return !findInStringMap(map, (value, key) => !predicate(value, key)); + } + + /** Array of every *defined* result of `mapAndFilter(value, key)`, called on each entry in the map. */ + export function mapAndFilterStringMap(map: StringMap, mapAndFilter: (value: V, key: string) => U | undefined): U[] { + const result: U[] = []; + map.forEach((value, key) => { + const entry = mapAndFilter(value, key); + if (entry !== undefined) { + result.push(entry); + } + }); + return result; + } + + /** + * Adds the value to an array of values associated with the key, and return the array. + * Creates the array if it does not already exist. + */ + export function multiMapAdd(map: MapCommon, key: K, value: V): V[] { + const values = map.get(key); + if (values) { + values.push(value); + return values; + } + else { + return setAndReturn(map, key, [value]); + } + } + + /** Creates a map with the given keys and values for each entry in `inputs`. */ + export function createStringMapFromArray(inputs: A[], getKey: (element: A) => string, getValue: (element: A) => V): StringMap { + const result = new StringMap(); + for (const input of inputs) { + result.set(getKey(input), getValue(input)); + } + return result; + } + + /** Map whose keys are `keys` and whose values are the results of `getValue`. */ + export function createStringMapFromKeys(keys: string[], getValue: (key: string) => V): StringMap { + return createStringMapFromArray(keys, key => key, getValue); + } + + /** + * Map whose values are `values` and whose keys are the results of `getKey`. + * `getKey` must not return the same key twice. + */ + export function createStringMapFromValues(values: V[], getKey: (value: V) => string): StringMap { + return createStringMapFromArray(values, getKey, value => value); + } + + export function createStringMapFromKeysAndValues(keys: string[], values: V[]): StringMap { + Debug.assert(keys.length === values.length); + const result = new StringMap(); + for (let i = 0; i < keys.length; i++) { + result.set(keys[i], values[i]); + } + return result; + } + + export function stringMapKeys(map: StringMap, getNewKey: (key: string) => string): StringMap { + const result = new StringMap(); + map.forEach((value, key) => { + result.set(getNewKey(key), value); + }); + return result; + } + + /** Modifies every value in the map by replacing it with the result of `getNewValue`. */ + export function mutateValues(map: StringMap, getNewValue: (value: V) => V): void { + map.forEach((value, key) => { + map.set(key, getNewValue(value)); + }); + } + + /** StringMap of a single entry. */ + export function createStringMap(key: string, value: V): StringMap { + return new StringMap([[key, value]]); + } +} + +// Set +namespace ts { + export interface StringSet { + /** Add a value if it's not already present. */ + add(value: string): void; + /** Remove a value. */ + delete(value: string): void; + /** Run `fn` on every value in the set. */ + forEach(fn: (value: string) => void): void; + /** Whether the value is in the set. */ + has(value: string): boolean; + } + export interface StringSetConstructor { + new(values?: string[]): StringSet; + } + + class ShimStringSet implements StringSet { + data: Map; + + constructor(values?: string[]) { + this.data = {}; + } + + add(value: string) { + this.data[value] = true; + } + + delete(value: string) { + delete this.data[value]; + } + + forEach(fn: (value: string) => void) { + forEachKey(this.data, fn); + } + + has(value: string) { + return hasProperty(this.data, value); + } + + isEmpty(): boolean { + for (const key in this.data) { + if (this.has(key)) { + return false; + } + } + return true; + } + } + + declare const Set: StringSetConstructor | undefined; + export const StringSet: StringSetConstructor = typeof Set === "undefined" ? ShimStringSet : Set; + + /** False iff there are any values in the set. */ + export function isSetEmpty(set: StringSet): boolean { + if (set instanceof ShimStringSet) { + return set.isEmpty(); + } + else { + // For native sets, this is available as a property. + return !(set).size; + } + } + + /** Add every value in `source` to `target`. */ + export function copySet(source: StringSet, target: StringSet): void { + source.forEach(element => target.add(element)); + } + + /** If `shouldBeInSet`, put `value` into the set; otherwise, remove it. */ + export function addOrDelete(set: StringSet, value: string, shouldBeInSet: boolean): void { + if (shouldBeInSet) { + set.add(value); + } + else { + set.delete(value); + } + } +} + +// Map +namespace ts { + export interface Map { + [index: string]: T; + } + + /** + * Reduce the properties of a map. + * + * @param map The map to reduce + * @param callback An aggregation function that is called for each entry in the map + * @param initial The initial value for the reduction. + */ + export function reduceProperties(map: Map, callback: (aggregate: U, value: T, key: string) => U, initial: U): U { + let result = initial; + if (map) { + for (const key in map) { + if (hasProperty(map, key)) { + result = callback(result, map[key], String(key)); + } + } + } + + return result; + } + + /** Convert a Map to a StringMap. */ + export function stringMapOfMap(map: Map): StringMap { + const result = new StringMap(); + for (const key in map) { + if (hasProperty(map, key)) { + result.set(key, map[key]); + } + } + return result; + } + + export function forEachValueAndKey(map: Map, callback: (value: T, key: string) => void): void { + for (const id in map) { + if (hasProperty(map, id)) { + callback(map[id], id); + } + } + } + + export function forEachKey(map: Map, callback: (key: string) => U): U { + let result: U; + for (const id in map) { + if (result = callback(id)) break; + } + return result; + } + + export function forEachValue(map: Map, callback: (value: T) => U): U { + let result: U; + for (const id in map) { + if (result = callback(map[id])) break; + } + return result; + } + + const hasOwnProperty = Object.prototype.hasOwnProperty; + + export function hasProperty(map: Map, key: string): boolean { + return hasOwnProperty.call(map, key); + } + + export function getKeys(map: Map): string[] { + const keys: string[] = []; + for (const key in map) { + keys.push(key); + } + return keys; + } + + export function getProperty(map: Map, key: string): T { + return hasProperty(map, key) ? map[key] : undefined; + } + + export function getOrUpdateProperty(map: Map, key: string, makeValue: () => T): T { + return hasProperty(map, key) ? map[key] : map[key] = makeValue(); + } + + export function isEmpty(map: Map) { + for (const id in map) { + if (hasProperty(map, id)) { + return false; + } + } + return true; + } + + export function clone(object: T): T { + const result: any = {}; + for (const id in object) { + result[id] = (object)[id]; + } + return result; + } + + export function extend, T2 extends Map<{}>>(first: T1 , second: T2): T1 & T2 { + const result: T1 & T2 = {}; + for (const id in first) { + (result as any)[id] = first[id]; + } + for (const id in second) { + if (!hasProperty(result, id)) { + (result as any)[id] = second[id]; + } + } + return result; + } + + export function mapIsEqualTo(map1: Map, map2: Map): boolean { + if (!map1 || !map2) { + return map1 === map2; + } + return containsAll(map1, map2) && containsAll(map2, map1); + } + + function containsAll(map: Map, other: Map): boolean { + for (const key in map) { + if (!hasProperty(map, key)) { + continue; + } + if (!hasProperty(other, key) || map[key] !== other[key]) { + return false; + } + } + return true; + } + + /** Copy all entries from `source` to `target`, overwriting any already existing. */ + export function copyMap(source: Map, target: Map): void { + forEachValueAndKey(source, (value, key) => { + target[key] = value; + }); + } +} + +// Array +namespace ts { + /** + * Tests whether a value is an array. + */ + export function isArray(value: any): value is any[] { + return Array.isArray ? Array.isArray(value) : value instanceof Array; + } + + /** + * Iterates through 'array' by index and performs the callback on each element of array until the callback + * returns a truthy value, then returns that value. + * If no such value is found, the callback is applied to each element of array and undefined is returned. + */ + export function forEach(array: T[], callback: (element: T, index: number) => U): U { + if (array) { + for (let i = 0, len = array.length; i < len; i++) { + const result = callback(array[i], i); + if (result) { + return result; + } + } + } + return undefined; + } + + export function contains(array: T[], value: T): boolean { + if (array) { + for (const v of array) { + if (v === value) { + return true; + } + } + } + return false; + } + + export function indexOf(array: T[], value: T): number { + if (array) { + for (let i = 0, len = array.length; i < len; i++) { + if (array[i] === value) { + return i; + } + } + } + return -1; + } + + export function countWhere(array: T[], predicate: (x: T) => boolean): number { + let count = 0; + if (array) { + for (const v of array) { + if (predicate(v)) { + count++; + } + } + } + return count; + } + + export function filter(array: T[], f: (x: T) => boolean): T[] { + let result: T[]; + if (array) { + result = []; + for (const item of array) { + if (f(item)) { + result.push(item); + } + } + } + return result; + } + + export function filterMutate(array: T[], f: (x: T) => boolean): void { + let outIndex = 0; + for (const item of array) { + if (f(item)) { + array[outIndex] = item; + outIndex++; + } + } + array.length = outIndex; + } + + export function map(array: T[], f: (x: T) => U): U[] { + let result: U[]; + if (array) { + result = []; + for (const v of array) { + result.push(f(v)); + } + } + return result; + } + + export function concatenate(array1: T[], array2: T[]): T[] { + if (!array2 || !array2.length) return array1; + if (!array1 || !array1.length) return array2; + + return array1.concat(array2); + } + + export function deduplicate(array: T[], areEqual?: (a: T, b: T) => boolean): T[] { + let result: T[]; + if (array) { + result = []; + loop: for (const item of array) { + for (const res of result) { + if (areEqual ? areEqual(res, item) : res === item) { + continue loop; + } + } + result.push(item); + } + } + return result; + } + + export function sum(array: any[], prop: string): number { + let result = 0; + for (const v of array) { + result += v[prop]; + } + return result; + } + + export function addRange(to: T[], from: T[]): void { + if (to && from) { + for (const v of from) { + to.push(v); + } + } + } + + export function rangeEquals(array1: T[], array2: T[], pos: number, end: number) { + while (pos < end) { + if (array1[pos] !== array2[pos]) { + return false; + } + pos++; + } + return true; + } + + /** + * Returns the last element of an array if non-empty, undefined otherwise. + */ + export function lastOrUndefined(array: T[]): T { + if (array.length === 0) { + return undefined; + } + + return array[array.length - 1]; + } + + /** + * Performs a binary search, finding the index at which 'value' occurs in 'array'. + * If no such index is found, returns the 2's-complement of first index at which + * number[index] exceeds number. + * @param array A sorted array whose first element must be no larger than number + * @param number The value to be searched for in the array. + */ + export function binarySearch(array: number[], value: number): number { + let low = 0; + let high = array.length - 1; + + while (low <= high) { + const middle = low + ((high - low) >> 1); + const midValue = array[middle]; + + if (midValue === value) { + return middle; + } + else if (midValue > value) { + high = middle - 1; + } + else { + low = middle + 1; + } + } + + return ~low; + } + + export function reduceLeft(array: T[], f: (a: T, x: T) => T): T; + export function reduceLeft(array: T[], f: (a: U, x: T) => U, initial: U): U; + export function reduceLeft(array: T[], f: (a: U, x: T) => U, initial?: U): U { + if (array) { + const count = array.length; + if (count > 0) { + let pos = 0; + let result: T | U; + if (arguments.length <= 2) { + result = array[pos]; + pos++; + } + else { + result = initial; + } + while (pos < count) { + result = f(result, array[pos]); + pos++; + } + return result; + } + } + return initial; + } + + export function reduceRight(array: T[], f: (a: T, x: T) => T): T; + export function reduceRight(array: T[], f: (a: U, x: T) => U, initial: U): U; + export function reduceRight(array: T[], f: (a: U, x: T) => U, initial?: U): U { + if (array) { + let pos = array.length - 1; + if (pos >= 0) { + let result: T | U; + if (arguments.length <= 2) { + result = array[pos]; + pos--; + } + else { + result = initial; + } + while (pos >= 0) { + result = f(result, array[pos]); + pos--; + } + return result; + } + } + return initial; + } + + export function arrayIsEqualTo(array1: T[], array2: T[], equaler?: (a: T, b: T) => boolean): boolean { + if (!array1 || !array2) { + return array1 === array2; + } + + if (array1.length !== array2.length) { + return false; + } + + for (let i = 0; i < array1.length; i++) { + const equals = equaler ? equaler(array1[i], array2[i]) : array1[i] === array2[i]; + if (!equals) { + return false; + } + } + + return true; + } +} diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index d93a8a0aed027..17e7fc3267f83 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: StringMap; let isCurrentFileExternalModule: boolean; let reportedDeclarationError = false; let errorNameNode: DeclarationName; @@ -537,14 +537,14 @@ namespace ts { // do not need to keep track of created temp names. function getExportDefaultTempVariableName(): string { const baseName = "_default"; - if (!hasProperty(currentIdentifiers, baseName)) { + if (!currentIdentifiers.has(baseName)) { return baseName; } let count = 0; while (true) { count++; const name = baseName + "_" + count; - if (!hasProperty(currentIdentifiers, name)) { + if (!currentIdentifiers.has(name)) { return name; } } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index c8bd8072b2400..a3cc4435fbca4 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -407,9 +407,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge function isUniqueLocalName(name: string, container: Node): boolean { for (let node = container; isNodeDescendentOf(node, container); node = node.nextContainer) { - if (node.locals && hasProperty(node.locals, name)) { + if (node.locals) { + const symbol = 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 (symbol && symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias)) { return false; } } @@ -530,8 +531,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge let currentSourceFile: SourceFile; let currentText: string; let currentLineMap: number[]; - let currentFileIdentifiers: Map; - let renamedDependencies: Map; + let currentFileIdentifiers: StringMap; + let renamedDependencies: StringMap; let isEs6Module: boolean; let isCurrentFileExternalModule: boolean; @@ -658,7 +659,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge currentLineMap = getLineStarts(sourceFile); exportFunctionForFile = undefined; contextObjectForFile = undefined; - isEs6Module = sourceFile.symbol && sourceFile.symbol.exports && !!sourceFile.symbol.exports["___esModule"]; + isEs6Module = sourceFile.symbol && sourceFile.symbol.exports && !!sourceFile.symbol.exports.get("___esModule"); renamedDependencies = sourceFile.renamedDependencies; currentFileIdentifiers = sourceFile.identifiers; isCurrentFileExternalModule = isExternalModule(sourceFile); @@ -669,7 +670,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge function isUniqueName(name: string): boolean { return !resolver.hasGlobalName(name) && - !hasProperty(currentFileIdentifiers, name) && + !currentFileIdentifiers.has(name) && !hasProperty(generatedNameSet, name); } @@ -6461,10 +6462,8 @@ const _super = (function (geti, seti) { * Here we check if alternative name was provided for a given moduleName and return it if possible. */ function tryRenameExternalModule(moduleName: LiteralExpression): string { - if (renamedDependencies && hasProperty(renamedDependencies, moduleName.text)) { - return `"${renamedDependencies[moduleName.text]}"`; - } - return undefined; + const rename = renamedDependencies && renamedDependencies.get(moduleName.text); + return rename ? `"${rename}"` : undefined; } function emitRequire(moduleName: Expression) { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 014d092621368..254664341d000 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -485,7 +485,7 @@ namespace ts { let currentToken: SyntaxKind; let sourceText: string; let nodeCount: number; - let identifiers: Map; + let identifiers: StringMap; let identifierCount: number; let parsingContext: ParsingContext; @@ -595,7 +595,7 @@ namespace ts { parseDiagnostics = []; parsingContext = 0; - identifiers = {}; + identifiers = new StringMap(); identifierCount = 0; nodeCount = 0; @@ -1084,7 +1084,7 @@ namespace ts { function internIdentifier(text: string): string { text = escapeIdentifier(text); - return hasProperty(identifiers, text) ? identifiers[text] : (identifiers[text] = text); + return getOrUpdate(identifiers, text, () => text); } // An identifier that starts with two underscores has an extra underscore character prepended to it to avoid issues diff --git a/src/compiler/performance.ts b/src/compiler/performance.ts index 89db876ae5e48..43d17fc1449f4 100644 --- a/src/compiler/performance.ts +++ b/src/compiler/performance.ts @@ -10,8 +10,8 @@ namespace ts.performance { /** Performance measurements for the compiler. */ declare const onProfilerEvent: { (markName: string): void; profiler: boolean; }; let profilerEvent: (markName: string) => void; - let counters: Map; - let measures: Map; + let counters: StringMap; + let measures: StringMap; /** * Emit a performance event if ts-profiler is connected. This is primarily used @@ -32,7 +32,7 @@ namespace ts.performance { */ export function increment(counterName: string) { if (counters) { - counters[counterName] = (getProperty(counters, counterName) || 0) + 1; + counters.set(counterName, (counters.get(counterName)) || 0 + 1); } } @@ -42,7 +42,7 @@ namespace ts.performance { * @param counterName The name of the counter. */ export function getCount(counterName: string) { - return counters && getProperty(counters, counterName) || 0; + return counters && counters.get(counterName) || 0; } /** @@ -60,17 +60,17 @@ namespace ts.performance { */ export function measure(measureName: string, marker: number) { if (measures) { - measures[measureName] = (getProperty(measures, measureName) || 0) + (timestamp() - marker); + measures.set(measureName, (measures.get(measureName) || 0) + (timestamp() - marker)); } } /** * Iterate over each measure, performing some action - * + * * @param cb The action to perform for each measure */ export function forEachMeasure(cb: (measureName: string, duration: number) => void) { - return forEachKey(measures, key => cb(key, measures[key])); + measures.forEach((value, key) => cb(key, value)); } /** @@ -79,21 +79,13 @@ namespace ts.performance { * @param measureName The name of the measure whose durations should be accumulated. */ export function getDuration(measureName: string) { - return measures && getProperty(measures, measureName) || 0; + return measures && measures.get(measureName) || 0; } /** Enables (and resets) performance measurements for the compiler. */ export function enable() { - counters = { }; - measures = { - "I/O Read": 0, - "I/O Write": 0, - "Program": 0, - "Parse": 0, - "Bind": 0, - "Check": 0, - "Emit": 0, - }; + counters = new StringMap(); + measures = createStringMapFromKeys( ["I/O Read", "I/O Write", "Program", "Parse", "Bind", "Check", "Emit"], () => 0); profilerEvent = typeof onProfilerEvent === "function" && onProfilerEvent.profiler === true ? onProfilerEvent diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 7d40b2f3219fc..7c8d3f099aa7d 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -846,7 +846,7 @@ namespace ts { } export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost { - const existingDirectories: Map = {}; + const existingDirectories = new StringSet(); function getCanonicalFileName(fileName: string): string { // if underlying system can distinguish between two files whose names differs only in cases then file name already in canonical form. @@ -877,11 +877,11 @@ namespace ts { } function directoryExists(directoryPath: string): boolean { - if (hasProperty(existingDirectories, directoryPath)) { + if (existingDirectories.has(directoryPath)) { return true; } if (sys.directoryExists(directoryPath)) { - existingDirectories[directoryPath] = true; + existingDirectories.add(directoryPath); return true; } return false; @@ -895,21 +895,20 @@ namespace ts { } } - let outputFingerprints: Map; + let outputFingerprints: StringMap; function writeFileIfUpdated(fileName: string, data: string, writeByteOrderMark: boolean): void { if (!outputFingerprints) { - outputFingerprints = {}; + outputFingerprints = new StringMap(); } const hash = sys.createHash(data); const mtimeBefore = sys.getModifiedTime(fileName); - if (mtimeBefore && hasProperty(outputFingerprints, fileName)) { - 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; @@ -920,11 +919,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) { @@ -1040,16 +1039,9 @@ namespace ts { return []; } const resolutions: T[] = []; - const cache: Map = {}; + const cache = new StringMap(); for (const name of names) { - let result: T; - if (hasProperty(cache, name)) { - result = cache[name]; - } - else { - result = loader(name, containingFile); - cache[name] = result; - } + const result = getOrUpdate(cache, name, () => loader(name, containingFile)); resolutions.push(result); } return resolutions; @@ -1093,9 +1085,9 @@ namespace ts { let commonSourceDirectory: string; let diagnosticsProducingTypeChecker: TypeChecker; let noDiagnosticsTypeChecker: TypeChecker; - let classifiableNames: Map; + let classifiableNames: StringSet; - let resolvedTypeReferenceDirectives: Map = {}; + let resolvedTypeReferenceDirectives = new StringMap(); let fileProcessingDiagnostics = createDiagnosticCollection(); // The below settings are to track if a .js file should be add to the program if loaded via searching under node_modules. @@ -1110,10 +1102,10 @@ namespace ts { // If a module has some of its imports skipped due to being at the depth limit under node_modules, then track // this, as it may be imported at a shallower depth later, and then it will need its skipped imports processed. - const modulesWithElidedImports: Map = {}; + const modulesWithElidedImports = new StringSet(); // Track source files that are source files found by searching under node_modules, as these shouldn't be compiled. - const sourceFilesFoundSearchingNodeModules: Map = {}; + const sourceFilesFoundSearchingNodeModules = new StringSet(); const start = performance.mark(); @@ -1241,10 +1233,10 @@ namespace ts { if (!classifiableNames) { // Initialize a checker so that all our files are bound. getTypeChecker(); - classifiableNames = {}; + classifiableNames = new StringSet(); for (const sourceFile of files) { - copyMap(sourceFile.classifiableNames, classifiableNames); + copySet(sourceFile.classifiableNames, classifiableNames); } } @@ -1394,7 +1386,7 @@ namespace ts { getSourceFile: program.getSourceFile, getSourceFileByPath: program.getSourceFileByPath, getSourceFiles: program.getSourceFiles, - isSourceFileFromExternalLibrary: (file: SourceFile) => !!lookUp(sourceFilesFoundSearchingNodeModules, file.path), + isSourceFileFromExternalLibrary: (file: SourceFile) => sourceFilesFoundSearchingNodeModules.has(file.path), writeFile: writeFileCallback || ( (fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)), isEmitBlocked, @@ -1931,20 +1923,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 && lookUp(sourceFilesFoundSearchingNodeModules, file.path) && currentNodeModulesDepth == 0) { - sourceFilesFoundSearchingNodeModules[file.path] = false; + if (file && sourceFilesFoundSearchingNodeModules.has(file.path) && currentNodeModulesDepth == 0) { + sourceFilesFoundSearchingNodeModules.delete(file.path); if (!options.noResolve) { processReferencedFiles(file, getDirectoryPath(fileName), isDefaultLib); processTypeReferenceDirectives(file); } - modulesWithElidedImports[file.path] = false; + modulesWithElidedImports.delete(file.path); processImportedModules(file, getDirectoryPath(fileName)); } // See if we need to reprocess the imports due to prior skipped imports - else if (file && lookUp(modulesWithElidedImports, file.path)) { + else if (file && modulesWithElidedImports.has(file.path)) { if (currentNodeModulesDepth < maxNodeModulesJsDepth) { - modulesWithElidedImports[file.path] = false; + modulesWithElidedImports.delete(file.path); processImportedModules(file, getDirectoryPath(fileName)); } } @@ -1965,7 +1957,7 @@ namespace ts { filesByName.set(path, file); if (file) { - sourceFilesFoundSearchingNodeModules[path] = (currentNodeModulesDepth > 0); + addOrDelete(sourceFilesFoundSearchingNodeModules, path, currentNodeModulesDepth > 0); file.path = path; if (host.useCaseSensitiveFileNames()) { @@ -2025,7 +2017,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; } @@ -2062,7 +2054,7 @@ namespace ts { } if (saveResolution) { - resolvedTypeReferenceDirectives[typeReferenceDirective] = resolvedTypeReferenceDirective; + resolvedTypeReferenceDirectives.set(typeReferenceDirective, resolvedTypeReferenceDirective); } } @@ -2082,7 +2074,7 @@ namespace ts { function processImportedModules(file: SourceFile, basePath: string) { collectExternalModuleReferences(file); if (file.imports.length || file.moduleAugmentations.length) { - file.resolvedModules = {}; + file.resolvedModules = new StringMap(); const moduleNames = map(concatenate(file.imports, file.moduleAugmentations), getTextOfLiteral); const resolutions = resolveModuleNamesWorker(moduleNames, getNormalizedAbsolutePath(file.fileName, currentDirectory)); for (let i = 0; i < moduleNames.length; i++) { @@ -2106,7 +2098,7 @@ namespace ts { const shouldAddFile = resolution && !options.noResolve && i < file.imports.length && !elideImport; if (elideImport) { - modulesWithElidedImports[file.path] = true; + modulesWithElidedImports.add(file.path); } else if (shouldAddFile) { findSourceFile(resolution.resolvedFileName, @@ -2191,18 +2183,15 @@ namespace ts { } if (options.paths) { - for (const key in options.paths) { - if (!hasProperty(options.paths, key)) { - continue; - } + ts.forEachValueAndKey(options.paths, (value, key) => { if (!hasZeroOrOneAsteriskCharacter(key)) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, key)); } - if (isArray(options.paths[key])) { - if (options.paths[key].length === 0) { + if (isArray(value)) { + if (value.length === 0) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Substitutions_for_pattern_0_shouldn_t_be_an_empty_array, key)); } - for (const subst of options.paths[key]) { + for (const subst of value) { const typeOfSubst = typeof subst; if (typeOfSubst === "string") { if (!hasZeroOrOneAsteriskCharacter(subst)) { @@ -2217,7 +2206,7 @@ namespace ts { else { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Substitutions_for_pattern_0_should_be_an_array, key)); } - } + }); } if (!options.sourceMap && !options.inlineSourceMap) { diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 6dd84298008ce..8f45c37c43463 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -55,131 +55,131 @@ namespace ts { tryScan(callback: () => T): T; } - const textToToken: Map = { - "abstract": SyntaxKind.AbstractKeyword, - "any": SyntaxKind.AnyKeyword, - "as": SyntaxKind.AsKeyword, - "boolean": SyntaxKind.BooleanKeyword, - "break": SyntaxKind.BreakKeyword, - "case": SyntaxKind.CaseKeyword, - "catch": SyntaxKind.CatchKeyword, - "class": SyntaxKind.ClassKeyword, - "continue": SyntaxKind.ContinueKeyword, - "const": SyntaxKind.ConstKeyword, - "constructor": SyntaxKind.ConstructorKeyword, - "debugger": SyntaxKind.DebuggerKeyword, - "declare": SyntaxKind.DeclareKeyword, - "default": SyntaxKind.DefaultKeyword, - "delete": SyntaxKind.DeleteKeyword, - "do": SyntaxKind.DoKeyword, - "else": SyntaxKind.ElseKeyword, - "enum": SyntaxKind.EnumKeyword, - "export": SyntaxKind.ExportKeyword, - "extends": SyntaxKind.ExtendsKeyword, - "false": SyntaxKind.FalseKeyword, - "finally": SyntaxKind.FinallyKeyword, - "for": SyntaxKind.ForKeyword, - "from": SyntaxKind.FromKeyword, - "function": SyntaxKind.FunctionKeyword, - "get": SyntaxKind.GetKeyword, - "if": SyntaxKind.IfKeyword, - "implements": SyntaxKind.ImplementsKeyword, - "import": SyntaxKind.ImportKeyword, - "in": SyntaxKind.InKeyword, - "instanceof": SyntaxKind.InstanceOfKeyword, - "interface": SyntaxKind.InterfaceKeyword, - "is": SyntaxKind.IsKeyword, - "let": SyntaxKind.LetKeyword, - "module": SyntaxKind.ModuleKeyword, - "namespace": SyntaxKind.NamespaceKeyword, - "never": SyntaxKind.NeverKeyword, - "new": SyntaxKind.NewKeyword, - "null": SyntaxKind.NullKeyword, - "number": SyntaxKind.NumberKeyword, - "package": SyntaxKind.PackageKeyword, - "private": SyntaxKind.PrivateKeyword, - "protected": SyntaxKind.ProtectedKeyword, - "public": SyntaxKind.PublicKeyword, - "readonly": SyntaxKind.ReadonlyKeyword, - "require": SyntaxKind.RequireKeyword, - "global": SyntaxKind.GlobalKeyword, - "return": SyntaxKind.ReturnKeyword, - "set": SyntaxKind.SetKeyword, - "static": SyntaxKind.StaticKeyword, - "string": SyntaxKind.StringKeyword, - "super": SyntaxKind.SuperKeyword, - "switch": SyntaxKind.SwitchKeyword, - "symbol": SyntaxKind.SymbolKeyword, - "this": SyntaxKind.ThisKeyword, - "throw": SyntaxKind.ThrowKeyword, - "true": SyntaxKind.TrueKeyword, - "try": SyntaxKind.TryKeyword, - "type": SyntaxKind.TypeKeyword, - "typeof": SyntaxKind.TypeOfKeyword, - "undefined": SyntaxKind.UndefinedKeyword, - "var": SyntaxKind.VarKeyword, - "void": SyntaxKind.VoidKeyword, - "while": SyntaxKind.WhileKeyword, - "with": SyntaxKind.WithKeyword, - "yield": SyntaxKind.YieldKeyword, - "async": SyntaxKind.AsyncKeyword, - "await": SyntaxKind.AwaitKeyword, - "of": SyntaxKind.OfKeyword, - "{": SyntaxKind.OpenBraceToken, - "}": SyntaxKind.CloseBraceToken, - "(": SyntaxKind.OpenParenToken, - ")": SyntaxKind.CloseParenToken, - "[": SyntaxKind.OpenBracketToken, - "]": SyntaxKind.CloseBracketToken, - ".": SyntaxKind.DotToken, - "...": SyntaxKind.DotDotDotToken, - ";": SyntaxKind.SemicolonToken, - ",": SyntaxKind.CommaToken, - "<": SyntaxKind.LessThanToken, - ">": SyntaxKind.GreaterThanToken, - "<=": SyntaxKind.LessThanEqualsToken, - ">=": SyntaxKind.GreaterThanEqualsToken, - "==": SyntaxKind.EqualsEqualsToken, - "!=": SyntaxKind.ExclamationEqualsToken, - "===": SyntaxKind.EqualsEqualsEqualsToken, - "!==": SyntaxKind.ExclamationEqualsEqualsToken, - "=>": SyntaxKind.EqualsGreaterThanToken, - "+": SyntaxKind.PlusToken, - "-": SyntaxKind.MinusToken, - "**": SyntaxKind.AsteriskAsteriskToken, - "*": SyntaxKind.AsteriskToken, - "/": SyntaxKind.SlashToken, - "%": SyntaxKind.PercentToken, - "++": SyntaxKind.PlusPlusToken, - "--": SyntaxKind.MinusMinusToken, - "<<": SyntaxKind.LessThanLessThanToken, - ">": SyntaxKind.GreaterThanGreaterThanToken, - ">>>": SyntaxKind.GreaterThanGreaterThanGreaterThanToken, - "&": SyntaxKind.AmpersandToken, - "|": SyntaxKind.BarToken, - "^": SyntaxKind.CaretToken, - "!": SyntaxKind.ExclamationToken, - "~": SyntaxKind.TildeToken, - "&&": SyntaxKind.AmpersandAmpersandToken, - "||": SyntaxKind.BarBarToken, - "?": SyntaxKind.QuestionToken, - ":": SyntaxKind.ColonToken, - "=": SyntaxKind.EqualsToken, - "+=": SyntaxKind.PlusEqualsToken, - "-=": SyntaxKind.MinusEqualsToken, - "*=": SyntaxKind.AsteriskEqualsToken, - "**=": SyntaxKind.AsteriskAsteriskEqualsToken, - "/=": SyntaxKind.SlashEqualsToken, - "%=": SyntaxKind.PercentEqualsToken, - "<<=": SyntaxKind.LessThanLessThanEqualsToken, - ">>=": SyntaxKind.GreaterThanGreaterThanEqualsToken, - ">>>=": SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, - "&=": SyntaxKind.AmpersandEqualsToken, - "|=": SyntaxKind.BarEqualsToken, - "^=": SyntaxKind.CaretEqualsToken, - "@": SyntaxKind.AtToken, - }; + const textToToken = new StringMap([ + ["abstract", SyntaxKind.AbstractKeyword], + ["any", SyntaxKind.AnyKeyword], + ["as", SyntaxKind.AsKeyword], + ["boolean", SyntaxKind.BooleanKeyword], + ["break", SyntaxKind.BreakKeyword], + ["case", SyntaxKind.CaseKeyword], + ["catch", SyntaxKind.CatchKeyword], + ["class", SyntaxKind.ClassKeyword], + ["continue", SyntaxKind.ContinueKeyword], + ["const", SyntaxKind.ConstKeyword], + ["constructor", SyntaxKind.ConstructorKeyword], + ["debugger", SyntaxKind.DebuggerKeyword], + ["declare", SyntaxKind.DeclareKeyword], + ["default", SyntaxKind.DefaultKeyword], + ["delete", SyntaxKind.DeleteKeyword], + ["do", SyntaxKind.DoKeyword], + ["else", SyntaxKind.ElseKeyword], + ["enum", SyntaxKind.EnumKeyword], + ["export", SyntaxKind.ExportKeyword], + ["extends", SyntaxKind.ExtendsKeyword], + ["false", SyntaxKind.FalseKeyword], + ["finally", SyntaxKind.FinallyKeyword], + ["for", SyntaxKind.ForKeyword], + ["from", SyntaxKind.FromKeyword], + ["function", SyntaxKind.FunctionKeyword], + ["get", SyntaxKind.GetKeyword], + ["if", SyntaxKind.IfKeyword], + ["implements", SyntaxKind.ImplementsKeyword], + ["import", SyntaxKind.ImportKeyword], + ["in", SyntaxKind.InKeyword], + ["instanceof", SyntaxKind.InstanceOfKeyword], + ["interface", SyntaxKind.InterfaceKeyword], + ["is", SyntaxKind.IsKeyword], + ["let", SyntaxKind.LetKeyword], + ["module", SyntaxKind.ModuleKeyword], + ["namespace", SyntaxKind.NamespaceKeyword], + ["never", SyntaxKind.NeverKeyword], + ["new", SyntaxKind.NewKeyword], + ["null", SyntaxKind.NullKeyword], + ["number", SyntaxKind.NumberKeyword], + ["package", SyntaxKind.PackageKeyword], + ["private", SyntaxKind.PrivateKeyword], + ["protected", SyntaxKind.ProtectedKeyword], + ["public", SyntaxKind.PublicKeyword], + ["readonly", SyntaxKind.ReadonlyKeyword], + ["require", SyntaxKind.RequireKeyword], + ["global", SyntaxKind.GlobalKeyword], + ["return", SyntaxKind.ReturnKeyword], + ["set", SyntaxKind.SetKeyword], + ["static", SyntaxKind.StaticKeyword], + ["string", SyntaxKind.StringKeyword], + ["super", SyntaxKind.SuperKeyword], + ["switch", SyntaxKind.SwitchKeyword], + ["symbol", SyntaxKind.SymbolKeyword], + ["this", SyntaxKind.ThisKeyword], + ["throw", SyntaxKind.ThrowKeyword], + ["true", SyntaxKind.TrueKeyword], + ["try", SyntaxKind.TryKeyword], + ["type", SyntaxKind.TypeKeyword], + ["typeof", SyntaxKind.TypeOfKeyword], + ["undefined", SyntaxKind.UndefinedKeyword], + ["var", SyntaxKind.VarKeyword], + ["void", SyntaxKind.VoidKeyword], + ["while", SyntaxKind.WhileKeyword], + ["with", SyntaxKind.WithKeyword], + ["yield", SyntaxKind.YieldKeyword], + ["async", SyntaxKind.AsyncKeyword], + ["await", SyntaxKind.AwaitKeyword], + ["of", SyntaxKind.OfKeyword], + ["{", SyntaxKind.OpenBraceToken], + ["}", SyntaxKind.CloseBraceToken], + ["(", SyntaxKind.OpenParenToken], + [")", SyntaxKind.CloseParenToken], + ["[", SyntaxKind.OpenBracketToken], + ["]", SyntaxKind.CloseBracketToken], + [".", SyntaxKind.DotToken], + ["...", SyntaxKind.DotDotDotToken], + [";", SyntaxKind.SemicolonToken], + [",", SyntaxKind.CommaToken], + ["<", SyntaxKind.LessThanToken], + [">", SyntaxKind.GreaterThanToken], + ["<=", SyntaxKind.LessThanEqualsToken], + [">=", SyntaxKind.GreaterThanEqualsToken], + ["==", SyntaxKind.EqualsEqualsToken], + ["!=", SyntaxKind.ExclamationEqualsToken], + ["===", SyntaxKind.EqualsEqualsEqualsToken], + ["!==", SyntaxKind.ExclamationEqualsEqualsToken], + ["=>", SyntaxKind.EqualsGreaterThanToken], + ["+", SyntaxKind.PlusToken], + ["-", SyntaxKind.MinusToken], + ["**", SyntaxKind.AsteriskAsteriskToken], + ["*", SyntaxKind.AsteriskToken], + ["/", SyntaxKind.SlashToken], + ["%", SyntaxKind.PercentToken], + ["++", SyntaxKind.PlusPlusToken], + ["--", SyntaxKind.MinusMinusToken], + ["<<", SyntaxKind.LessThanLessThanToken], + [">", SyntaxKind.GreaterThanGreaterThanToken], + [">>>", SyntaxKind.GreaterThanGreaterThanGreaterThanToken], + ["&", SyntaxKind.AmpersandToken], + ["|", SyntaxKind.BarToken], + ["^", SyntaxKind.CaretToken], + ["!", SyntaxKind.ExclamationToken], + ["~", SyntaxKind.TildeToken], + ["&&", SyntaxKind.AmpersandAmpersandToken], + ["||", SyntaxKind.BarBarToken], + ["?", SyntaxKind.QuestionToken], + [":", SyntaxKind.ColonToken], + ["=", SyntaxKind.EqualsToken], + ["+=", SyntaxKind.PlusEqualsToken], + ["-=", SyntaxKind.MinusEqualsToken], + ["*=", SyntaxKind.AsteriskEqualsToken], + ["**=", SyntaxKind.AsteriskAsteriskEqualsToken], + ["/=", SyntaxKind.SlashEqualsToken], + ["%=", SyntaxKind.PercentEqualsToken], + ["<<=", SyntaxKind.LessThanLessThanEqualsToken], + [">>=", SyntaxKind.GreaterThanGreaterThanEqualsToken], + [">>>=", SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken], + ["&=", SyntaxKind.AmpersandEqualsToken], + ["|=", SyntaxKind.BarEqualsToken], + ["^=", SyntaxKind.CaretEqualsToken], + ["@", SyntaxKind.AtToken], + ]); /* As per ECMAScript Language Specification 3th Edition, Section 7.6: Identifiers @@ -271,13 +271,11 @@ namespace ts { lookupInUnicodeMap(code, unicodeES3IdentifierPart); } - function makeReverseMap(source: Map): string[] { + function makeReverseMap(source: StringMap): string[] { const result: string[] = []; - for (const name in source) { - if (source.hasOwnProperty(name)) { - result[source[name]] = name; - } - } + source.forEach((number, name) => { + result[number] = name; + }); return result; } @@ -288,8 +286,8 @@ namespace ts { } /* @internal */ - export function stringToToken(s: string): SyntaxKind { - return textToToken[s]; + export function stringToToken(s: string): SyntaxKind | undefined { + return textToToken.get(s); } /* @internal */ @@ -363,8 +361,6 @@ namespace ts { return computeLineAndCharacterOfPosition(getLineStarts(sourceFile), position); } - const hasOwnProperty = Object.prototype.hasOwnProperty; - export function isWhiteSpace(ch: number): boolean { return isWhiteSpaceSingleLine(ch) || isLineBreak(ch); } @@ -1128,8 +1124,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) { + const tkn = textToToken.get(tokenValue); + if (tkn) { + return token = tkn; + } } } return token = SyntaxKind.Identifier; diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 29ae2c60af165..4eaa2c53a676f 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -233,27 +233,27 @@ namespace ts { const useNonPollingWatchers = process.env["TSC_NONPOLLING_WATCHER"]; function createWatchedFileSet() { - const dirWatchers: Map = {}; + const dirWatchers = new StringMap(); // One file can have multiple watchers - const fileWatcherCallbacks: Map = {}; + const fileWatcherCallbacks = new StringMap(); return { addFile, removeFile }; function reduceDirWatcherRefCountForFile(fileName: string) { const dirName = getDirectoryPath(fileName); - if (hasProperty(dirWatchers, dirName)) { - 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 { - if (hasProperty(dirWatchers, dirPath)) { - const watcher = dirWatchers[dirPath]; - watcher.referenceCount += 1; + const existingWatcher = dirWatchers.get(dirPath); + if (existingWatcher) { + existingWatcher.referenceCount += 1; return; } @@ -263,17 +263,12 @@ namespace ts { (eventName: string, relativeFileName: string) => fileEventHandler(eventName, relativeFileName, dirPath) ); watcher.referenceCount = 1; - dirWatchers[dirPath] = watcher; + dirWatchers.set(dirPath, watcher); return; } function addFileWatcherCallback(filePath: string, callback: FileWatcherCallback): void { - if (hasProperty(fileWatcherCallbacks, filePath)) { - fileWatcherCallbacks[filePath].push(callback); - } - else { - fileWatcherCallbacks[filePath] = [callback]; - } + multiMapAdd(fileWatcherCallbacks, filePath, callback); } function addFile(fileName: string, callback: FileWatcherCallback): WatchedFile { @@ -289,13 +284,14 @@ namespace ts { } function removeFileWatcherCallback(filePath: string, callback: FileWatcherCallback) { - if (hasProperty(fileWatcherCallbacks, filePath)) { - const newCallbacks = copyListRemovingItem(callback, fileWatcherCallbacks[filePath]); + const callbacks = fileWatcherCallbacks.get(filePath); + if (callbacks) { + const newCallbacks = copyListRemovingItem(callback, callbacks); if (newCallbacks.length === 0) { - delete fileWatcherCallbacks[filePath]; + fileWatcherCallbacks.delete(filePath); } else { - fileWatcherCallbacks[filePath] = newCallbacks; + fileWatcherCallbacks.set(filePath, newCallbacks); } } } @@ -306,9 +302,12 @@ namespace ts { ? undefined : ts.getNormalizedAbsolutePath(relativeFileName, baseDirPath); // Some applications save a working file via rename operations - if ((eventName === "change" || eventName === "rename") && hasProperty(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/tsc.ts b/src/compiler/tsc.ts index 10538d0c009ee..5eb04f38ba7af 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -122,11 +122,11 @@ namespace ts { const gutterSeparator = " "; const resetEscapeSequence = "\u001b[0m"; const ellipsis = "..."; - const categoryFormatMap: Map = { - [DiagnosticCategory.Warning]: yellowForegroundEscapeSequence, - [DiagnosticCategory.Error]: redForegroundEscapeSequence, - [DiagnosticCategory.Message]: blueForegroundEscapeSequence, - }; + const categoryFormatMap = new NumberMap([ + [DiagnosticCategory.Warning, yellowForegroundEscapeSequence], + [DiagnosticCategory.Error, redForegroundEscapeSequence], + [DiagnosticCategory.Message, blueForegroundEscapeSequence], + ]); function formatAndReset(text: string, formatStyle: string) { return formatStyle + text + resetEscapeSequence; @@ -194,7 +194,7 @@ namespace ts { output += `${ relativeFileName }(${ firstLine + 1 },${ firstLineChar + 1 }): `; } - const categoryColor = categoryFormatMap[diagnostic.category]; + const categoryColor = categoryFormatMap.get(diagnostic.category); const category = DiagnosticCategory[diagnostic.category].toLowerCase(); output += `${ formatAndReset(category, categoryColor) } TS${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }`; output += sys.newLine + sys.newLine; @@ -262,7 +262,7 @@ namespace ts { // This map stores and reuses results of fileExists check that happen inside 'createProgram' // This allows to save time in module resolution heavy scenarios when existence of the same file might be checked multiple times. - let cachedExistingFiles: Map; + let cachedExistingFiles: StringMap; let hostFileExists: typeof compilerHost.fileExists; if (commandLine.options.locale) { @@ -432,7 +432,7 @@ namespace ts { } // reset the cache of existing files - cachedExistingFiles = {}; + cachedExistingFiles = new StringMap(); const compileResult = compile(rootFileNames, compilerOptions, compilerHost); @@ -445,10 +445,7 @@ namespace ts { } function cachedFileExists(fileName: string): boolean { - if (hasProperty(cachedExistingFiles, fileName)) { - return cachedExistingFiles[fileName]; - } - return cachedExistingFiles[fileName] = hostFileExists(fileName); + return getOrUpdate(cachedExistingFiles, fileName, () => hostFileExists(fileName)); } function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void) { @@ -676,7 +673,7 @@ namespace ts { const usageColumn: string[] = []; // Things like "-d, --declaration" go in here. const descriptionColumn: string[] = []; - const optionsDescriptionMap: Map = {}; // Map between option.description and list of option.type if it is a kind + const optionsDescriptionMap = new StringMap(); // Map between option.description and list of option.type if it is a kind for (let i = 0; i < optsList.length; i++) { const option = optsList[i]; @@ -704,10 +701,10 @@ namespace ts { description = getDiagnosticText(option.description); const options: string[] = []; const element = (option).element; - forEachKey(>element.type, key => { + forEachKeyInStringMap(>element.type, key => { options.push(`'${key}'`); }); - optionsDescriptionMap[description] = options; + optionsDescriptionMap.set(description, options); } else { description = getDiagnosticText(option.description); @@ -729,7 +726,7 @@ namespace ts { for (let i = 0; i < usageColumn.length; i++) { const usage = usageColumn[i]; const description = descriptionColumn[i]; - const kindsList = optionsDescriptionMap[description]; + const kindsList = optionsDescriptionMap.get(description); output += usage + makePadding(marginLength - usage.length + 2) + description + sys.newLine; if (kindsList) { @@ -789,40 +786,36 @@ namespace ts { const result: Map = {}; const optionsNameMap = getOptionNameMap().optionNameMap; - for (const name in options) { - if (hasProperty(options, name)) { - // tsconfig only options cannot be specified via command line, - // so we can assume that only types that can appear here string | number | boolean - const value = options[name]; - switch (name) { - case "init": - case "watch": - case "version": - case "help": - case "project": - break; - default: - let optionDefinition = optionsNameMap[name.toLowerCase()]; - if (optionDefinition) { - if (typeof optionDefinition.type === "string") { - // string, number or boolean - result[name] = value; - } - else { - // Enum - const typeMap = >optionDefinition.type; - for (const key in typeMap) { - if (hasProperty(typeMap, key)) { - if (typeMap[key] === value) - result[name] = key; - } + // tsconfig only options cannot be specified via command line, + // so we can assume that only types that can appear here string | number | boolean + ts.forEachValueAndKey(>options, (value, name) => { + switch (name) { + case "init": + case "watch": + case "version": + case "help": + case "project": + break; + default: + let optionDefinition = optionsNameMap.get(name.toLowerCase()); + if (optionDefinition) { + if (typeof optionDefinition.type === "string") { + // string, number or boolean + result[name] = value; + } + else { + // Enum + const typeMap = >optionDefinition.type; + typeMap.forEach((enumValue, key) => { + if (enumValue === value) { + result[name] = key; } - } + }); } - break; - } + } + break; } - } + }); return result; } } diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index cc9bfddcece78..9c2ab0b79178a 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -11,6 +11,7 @@ "stripInternal": true }, "files": [ + "dataStructures.ts", "core.ts", "performance.ts", "sys.ts", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a6e860450c669..b72fe7481c53e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1,9 +1,6 @@ +/// namespace ts { - export interface Map { - [index: string]: T; - } - // branded string type used to store absolute, normalized and canonicalized paths // arbitrary file name can be converted to Path via toPath function export type Path = string & { __pathBrand: any }; @@ -1640,7 +1637,7 @@ namespace ts { // this map is used by transpiler to supply alternative names for dependencies (i.e. in case of bundling) /* @internal */ - renamedDependencies?: Map; + renamedDependencies?: StringMap; /** * lib.d.ts should have a reference comment like @@ -1660,7 +1657,7 @@ namespace ts { // The first node that causes this file to be a CommonJS module /* @internal */ commonJsModuleIndicator: Node; - /* @internal */ identifiers: Map; + /* @internal */ identifiers: StringMap; /* @internal */ nodeCount: number; /* @internal */ identifierCount: number; /* @internal */ symbolCount: number; @@ -1675,12 +1672,12 @@ namespace ts { // Stores a line map for the file. // This field should never be used directly to obtain line map, use getLineMap function instead. /* @internal */ lineMap: number[]; - /* @internal */ classifiableNames?: Map; + /* @internal */ classifiableNames?: StringSet; // Stores a mapping 'external module reference text' -> 'resolved file name' | undefined // It is used to resolve module names in the checker. // Content of this field should never be used directly - use getResolvedModuleFileName/setResolvedModuleFileName functions instead - /* @internal */ resolvedModules: Map; - /* @internal */ resolvedTypeReferenceDirectiveNames: Map; + /* @internal */ resolvedModules: StringMap; + /* @internal */ resolvedTypeReferenceDirectiveNames: StringMap; /* @internal */ imports: LiteralExpression[]; /* @internal */ moduleAugmentations: LiteralExpression[]; /* @internal */ patternAmbientModules?: PatternAmbientModule[]; @@ -1759,7 +1756,7 @@ namespace ts { // language service). /* @internal */ getDiagnosticsProducingTypeChecker(): TypeChecker; - /* @internal */ getClassifiableNames(): Map; + /* @internal */ getClassifiableNames(): StringSet; /* @internal */ getNodeCount(): number; /* @internal */ getIdentifierCount(): number; @@ -1767,7 +1764,7 @@ namespace ts { /* @internal */ getTypeCount(): number; /* @internal */ getFileProcessingDiagnostics(): DiagnosticCollection; - /* @internal */ getResolvedTypeReferenceDirectives(): Map; + /* @internal */ getResolvedTypeReferenceDirectives(): StringMap; // For testing purposes only. /* @internal */ structureIsReused?: boolean; } @@ -1828,7 +1825,7 @@ namespace ts { getSourceFiles(): SourceFile[]; getSourceFile(fileName: string): SourceFile; - getResolvedTypeReferenceDirectives(): Map; + getResolvedTypeReferenceDirectives(): StringMap; } export interface TypeChecker { @@ -2156,7 +2153,7 @@ namespace ts { declaredType?: Type; // Type of class, interface, enum, type alias, or type parameter typeParameters?: TypeParameter[]; // Type parameters of type alias (undefined if non-generic) inferredClassType?: Type; // Type of an inferred ES5 class - instantiations?: Map; // Instantiations of generic type alias (undefined if non-generic) + instantiations?: StringMap; // Instantiations of generic type alias (undefined if non-generic) mapper?: TypeMapper; // Type mapper for instantiation alias referenced?: boolean; // True if alias symbol has been referenced as a value containingType?: UnionOrIntersectionType; // Containing union or intersection type for synthetic property @@ -2172,9 +2169,7 @@ namespace ts { /* @internal */ export interface TransientSymbol extends Symbol, SymbolLinks { } - export interface SymbolTable { - [index: string]: Symbol; - } + export type SymbolTable = StringMap; /** Represents a "prefix*suffix" pattern. */ /* @internal */ @@ -2319,7 +2314,7 @@ namespace ts { // Enum types (TypeFlags.Enum) export interface EnumType extends Type { - memberTypes: Map; + memberTypes: NumberMap; } // Enum types (TypeFlags.EnumLiteral) @@ -2366,7 +2361,7 @@ namespace ts { // Generic class and interface types export interface GenericType extends InterfaceType, TypeReference { /* @internal */ - instantiations: Map; // Generic instantiation cache + instantiations: StringMap; // Generic instantiation cache } export interface TupleType extends ObjectType { @@ -2733,7 +2728,7 @@ namespace ts { /* @internal */ export interface CommandLineOptionBase { name: string; - type: "string" | "number" | "boolean" | "object" | "list" | Map; // a value of a primitive type, or an object literal mapping named values to actual values + type: "string" | "number" | "boolean" | "object" | "list" | StringMap; // a value of a primitive type, or an object literal mapping named values to actual values isFilePath?: boolean; // True if option value is a path or fileName shortName?: string; // A short mnemonic for convenience - for instance, 'h' can be used in place of 'help' description?: DiagnosticMessage; // The message describing what the command line switch does @@ -2749,7 +2744,7 @@ namespace ts { /* @internal */ export interface CommandLineOptionOfCustomType extends CommandLineOptionBase { - type: Map; // an object literal mapping named values to actual values + type: StringMap; // an object literal mapping named values to actual values } /* @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 2cfaf1f5cfbc4..a3f97915e186a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -87,66 +87,28 @@ namespace ts { return node.end - node.pos; } - export function mapIsEqualTo(map1: Map, map2: Map): boolean { - if (!map1 || !map2) { - return map1 === map2; - } - return containsAll(map1, map2) && containsAll(map2, map1); - } - - function containsAll(map: Map, other: Map): boolean { - for (const key in map) { - if (!hasProperty(map, key)) { - continue; - } - if (!hasProperty(other, key) || map[key] !== other[key]) { - return false; - } - } - return true; - } - - export function arrayIsEqualTo(array1: T[], array2: T[], equaler?: (a: T, b: T) => boolean): boolean { - if (!array1 || !array2) { - return array1 === array2; - } - - if (array1.length !== array2.length) { - return false; - } - - for (let i = 0; i < array1.length; i++) { - const equals = equaler ? equaler(array1[i], array2[i]) : array1[i] === array2[i]; - if (!equals) { - return false; - } - } - - return true; - } - export function hasResolvedModule(sourceFile: SourceFile, moduleNameText: string): boolean { - return sourceFile.resolvedModules && hasProperty(sourceFile.resolvedModules, moduleNameText); + return sourceFile.resolvedModules && sourceFile.resolvedModules.has(moduleNameText); } export function getResolvedModule(sourceFile: SourceFile, moduleNameText: string): ResolvedModule { - return hasResolvedModule(sourceFile, moduleNameText) ? sourceFile.resolvedModules[moduleNameText] : undefined; + return sourceFile.resolvedModules && sourceFile.resolvedModules.get(moduleNameText); } export function setResolvedModule(sourceFile: SourceFile, moduleNameText: string, resolvedModule: ResolvedModule): void { if (!sourceFile.resolvedModules) { - sourceFile.resolvedModules = {}; + sourceFile.resolvedModules = new StringMap(); } - sourceFile.resolvedModules[moduleNameText] = resolvedModule; + sourceFile.resolvedModules.set(moduleNameText, resolvedModule); } export function setResolvedTypeReferenceDirective(sourceFile: SourceFile, typeReferenceDirectiveName: string, resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective): void { if (!sourceFile.resolvedTypeReferenceDirectiveNames) { - sourceFile.resolvedTypeReferenceDirectiveNames = {}; + sourceFile.resolvedTypeReferenceDirectiveNames = new StringMap(); } - sourceFile.resolvedTypeReferenceDirectiveNames[typeReferenceDirectiveName] = resolvedTypeReferenceDirective; + sourceFile.resolvedTypeReferenceDirectiveNames.set(typeReferenceDirectiveName, resolvedTypeReferenceDirective); } /* @internal */ @@ -160,13 +122,13 @@ namespace ts { } /* @internal */ - export function hasChangesInResolutions(names: string[], newResolutions: T[], oldResolutions: Map, comparer: (oldResolution: T, newResolution: T) => boolean): boolean { + export function hasChangesInResolutions(names: string[], newResolutions: T[], oldResolutions: StringMap, comparer: (oldResolution: T, newResolution: T) => boolean): boolean { if (names.length !== newResolutions.length) { return false; } for (let i = 0; i < names.length; i++) { const newResolution = newResolutions[i]; - const oldResolution = oldResolutions && hasProperty(oldResolutions, names[i]) ? oldResolutions[names[i]] : undefined; + const oldResolution = oldResolutions && oldResolutions.get(names[i]); const changed = oldResolution ? !newResolution || !comparer(oldResolution, newResolution) @@ -1966,7 +1928,7 @@ namespace ts { export function createDiagnosticCollection(): DiagnosticCollection { let nonFileDiagnostics: Diagnostic[] = []; - const fileDiagnostics: Map = {}; + const fileDiagnostics = new StringMap(); let diagnosticsModified = false; let modificationCount = 0; @@ -1984,11 +1946,12 @@ namespace ts { } function reattachFileDiagnostics(newFile: SourceFile): void { - if (!hasProperty(fileDiagnostics, newFile.fileName)) { + const diagnostics = fileDiagnostics.get(newFile.fileName); + if (!diagnostics) { return; } - for (const diagnostic of fileDiagnostics[newFile.fileName]) { + for (const diagnostic of diagnostics) { diagnostic.file = newFile; } } @@ -1996,10 +1959,9 @@ namespace ts { function add(diagnostic: Diagnostic): void { let diagnostics: Diagnostic[]; if (diagnostic.file) { - diagnostics = fileDiagnostics[diagnostic.file.fileName]; + diagnostics = fileDiagnostics.get(diagnostic.file.fileName); if (!diagnostics) { - diagnostics = []; - fileDiagnostics[diagnostic.file.fileName] = diagnostics; + diagnostics = setAndReturn(fileDiagnostics, diagnostic.file.fileName, []); } } else { @@ -2019,7 +1981,7 @@ namespace ts { function getDiagnostics(fileName?: string): Diagnostic[] { sortAndDeduplicate(); if (fileName) { - return fileDiagnostics[fileName] || []; + return fileDiagnostics.get(fileName) || []; } const allDiagnostics: Diagnostic[] = []; @@ -2029,11 +1991,9 @@ namespace ts { forEach(nonFileDiagnostics, pushDiagnostic); - for (const key in fileDiagnostics) { - if (hasProperty(fileDiagnostics, key)) { - forEach(fileDiagnostics[key], pushDiagnostic); - } - } + fileDiagnostics.forEach(diagnostics => { + forEach(diagnostics, pushDiagnostic); + }); return sortAndDeduplicateDiagnostics(allDiagnostics); } @@ -2046,11 +2006,7 @@ namespace ts { diagnosticsModified = false; nonFileDiagnostics = sortAndDeduplicateDiagnostics(nonFileDiagnostics); - for (const key in fileDiagnostics) { - if (hasProperty(fileDiagnostics, key)) { - fileDiagnostics[key] = sortAndDeduplicateDiagnostics(fileDiagnostics[key]); - } - } + mutateValues(fileDiagnostics, sortAndDeduplicateDiagnostics); } } @@ -2060,20 +2016,20 @@ namespace ts { // the map below must be updated. Note that this regexp *does not* include the 'delete' character. // There is no reason for this other than that JSON.stringify does not handle it either. const escapedCharsRegExp = /[\\\"\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g; - const escapedCharsMap: Map = { - "\0": "\\0", - "\t": "\\t", - "\v": "\\v", - "\f": "\\f", - "\b": "\\b", - "\r": "\\r", - "\n": "\\n", - "\\": "\\\\", - "\"": "\\\"", - "\u2028": "\\u2028", // lineSeparator - "\u2029": "\\u2029", // paragraphSeparator - "\u0085": "\\u0085" // nextLine - }; + const escapedCharsMap = new StringMap([ + ["\0", "\\0"], + ["\t", "\\t"], + ["\v", "\\v"], + ["\f", "\\f"], + ["\b", "\\b"], + ["\r", "\\r"], + ["\n", "\\n"], + ["\\", "\\\\"], + ["\"", "\\\""], + ["\u2028", "\\u2028"], // lineSeparator + ["\u2029", "\\u2029"], // paragraphSeparator + ["\u0085", "\\u0085"] // nextLine + ]); /** @@ -2087,7 +2043,7 @@ namespace ts { return s; function getReplacement(c: string) { - return escapedCharsMap[c] || get16BitUnicodeEscapeSequence(c.charCodeAt(0)); + return escapedCharsMap.get(c) || get16BitUnicodeEscapeSequence(c.charCodeAt(0)); } } diff --git a/src/harness/compilerRunner.ts b/src/harness/compilerRunner.ts index ecdc18394b079..d625b7fd45956 100644 --- a/src/harness/compilerRunner.ts +++ b/src/harness/compilerRunner.ts @@ -291,12 +291,12 @@ class CompilerBaselineRunner extends RunnerBase { const fullWalker = new TypeWriterWalker(program, /*fullTypeCheck*/ true); - const fullResults: ts.Map = {}; - const pullResults: ts.Map = {}; + const fullResults = new ts.StringMap(); + const pullResults = new ts.StringMap(); for (const sourceFile of allFiles) { - fullResults[sourceFile.unitName] = fullWalker.getTypeAndSymbols(sourceFile.unitName); - pullResults[sourceFile.unitName] = fullWalker.getTypeAndSymbols(sourceFile.unitName); + fullResults.set(sourceFile.unitName, fullWalker.getTypeAndSymbols(sourceFile.unitName)); + pullResults.set(sourceFile.unitName, fullWalker.getTypeAndSymbols(sourceFile.unitName)); } // Produce baselines. The first gives the types for all expressions. @@ -338,37 +338,31 @@ class CompilerBaselineRunner extends RunnerBase { } } - function generateBaseLine(typeWriterResults: ts.Map, isSymbolBaseline: boolean): string { + function generateBaseLine(typeWriterResults: ts.StringMap, isSymbolBaseline: boolean): string { const typeLines: string[] = []; - const typeMap: { [fileName: string]: { [lineNum: number]: string[]; } } = {}; + // Maps fileName to a (lineNumber -> string[]) map. + const typeMap = new ts.StringMap>(); allFiles.forEach(file => { const codeLines = file.content.split("\n"); - typeWriterResults[file.unitName].forEach(result => { + typeWriterResults.get(file.unitName).forEach(result => { if (isSymbolBaseline && !result.symbol) { return; } const typeOrSymbolString = isSymbolBaseline ? result.symbol : result.type; const formattedLine = result.sourceText.replace(/\r?\n/g, "") + " : " + typeOrSymbolString; - if (!typeMap[file.unitName]) { - typeMap[file.unitName] = {}; - } - - let typeInfo = [formattedLine]; - const existingTypeInfo = typeMap[file.unitName][result.line]; - if (existingTypeInfo) { - typeInfo = existingTypeInfo.concat(typeInfo); - } - typeMap[file.unitName][result.line] = typeInfo; + const linesMap = ts.getOrUpdate(typeMap, file.unitName, () => new ts.NumberMap()); + ts.multiMapAdd(linesMap, result.line, formattedLine); }); typeLines.push("=== " + file.unitName + " ===\r\n"); for (let i = 0; i < codeLines.length; i++) { const currentCodeLine = codeLines[i]; typeLines.push(currentCodeLine + "\r\n"); - if (typeMap[file.unitName]) { - const typeInfo = typeMap[file.unitName][i]; + const typeInfos = typeMap.get(file.unitName); + if (typeInfos) { + const typeInfo = typeInfos.get(i); if (typeInfo) { typeInfo.forEach(ty => { typeLines.push(">" + ty + "\r\n"); diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index a42abbbc60909..739083d45757e 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -95,17 +95,17 @@ namespace FourSlash { export import IndentStyle = ts.IndentStyle; - const entityMap: ts.Map = { - "&": "&", - "\"": """, - "'": "'", - "/": "/", - "<": "<", - ">": ">" - }; + const entityMap = new ts.StringMap([ + ["&", "&"], + ["\"", """], + ["'", "'"], + ["/", "/"], + ["<", "<"], + [">", ">"] + ]); export function escapeXmlAttributeValue(s: string) { - return s.replace(/[&<>"'\/]/g, ch => entityMap[ch]); + return s.replace(/[&<>"'\/]/g, ch => entityMap.get(ch)); } // Name of testcase metadata including ts.CompilerOptions properties that will be used by globalOptions @@ -204,7 +204,7 @@ namespace FourSlash { public formatCodeOptions: ts.FormatCodeOptions; - private inputFiles: ts.Map = {}; // Map between inputFile's fileName and its content for easily looking up when resolving references + private inputFiles = new ts.StringMap(); // Map between inputFile's fileName and its content for easily looking up when resolving references // Add input file which has matched file name with the given reference-file path. // This is necessary when resolveReference flag is specified @@ -219,7 +219,7 @@ namespace FourSlash { } function tryAdd(path: string) { - const inputFile = inputFiles[path]; + const inputFile = inputFiles.get(path); if (inputFile && !Harness.isDefaultLibraryFile(path)) { languageServiceAdapterHost.addScript(path, inputFile, /*isRootFile*/ true); return true; @@ -259,7 +259,7 @@ namespace FourSlash { ts.forEach(testData.files, file => { // Create map between fileName and its content for easily looking up when resolveReference flag is specified - this.inputFiles[file.fileName] = file.content; + this.inputFiles.set(file.fileName, file.content); if (!startResolveFileRef && file.fileOptions[metadataOptionNames.resolveReference] === "true") { startResolveFileRef = file; } @@ -300,9 +300,9 @@ namespace FourSlash { } else { // resolveReference file-option is not specified then do not resolve any files and include all inputFiles - ts.forEachKey(this.inputFiles, fileName => { + this.inputFiles.forEach((inputFile, fileName) => { if (!Harness.isDefaultLibraryFile(fileName)) { - this.languageServiceAdapterHost.addScript(fileName, this.inputFiles[fileName], /*isRootFile*/ true); + this.languageServiceAdapterHost.addScript(fileName, inputFile, /*isRootFile*/ true); } }); this.languageServiceAdapterHost.addScript(Harness.Compiler.defaultLibFileName, @@ -593,13 +593,14 @@ namespace FourSlash { public noItemsWithSameNameButDifferentKind(): void { const completions = this.getCompletionListAtCaret(); - const uniqueItems: ts.Map = {}; + const uniqueItems = new ts.StringMap(); for (const item of completions.entries) { - if (!ts.hasProperty(uniqueItems, item.name)) { - uniqueItems[item.name] = item.kind; + const uniqueItem = uniqueItems.get(item.name); + if (!uniqueItem) { + uniqueItems.set(item.name, item.kind); } else { - assert.equal(item.kind, uniqueItems[item.name], `Items should have the same kind, got ${item.kind} and ${uniqueItems[item.name]}`); + assert.equal(item.kind, uniqueItem, `Items should have the same kind, got ${item.kind} and ${uniqueItem}`); } } } @@ -773,7 +774,7 @@ namespace FourSlash { } public verifyRangesWithSameTextReferenceEachOther() { - ts.forEachValue(this.rangesByText(), ranges => this.verifyRangesReferenceEachOther(ranges)); + this.rangesByText().forEach(ranges => this.verifyRangesReferenceEachOther(ranges)); } private verifyReferencesWorker(references: ts.ReferenceEntry[], fileName: string, start: number, end: number, isWriteAccess?: boolean, isDefinition?: boolean) { @@ -1638,11 +1639,11 @@ namespace FourSlash { return this.testData.ranges; } - public rangesByText(): ts.Map { - const result: ts.Map = {}; + public rangesByText(): ts.StringMap { + const result = new ts.StringMap(); for (const range of this.getRanges()) { const text = this.rangeText(range); - (ts.getProperty(result, text) || (result[text] = [])).push(range); + ts.multiMapAdd(result, text, range); } return result; } @@ -1897,17 +1898,17 @@ namespace FourSlash { public verifyBraceCompletionAtPosition(negative: boolean, openingBrace: string) { - const openBraceMap: ts.Map = { - "(": ts.CharacterCodes.openParen, - "{": ts.CharacterCodes.openBrace, - "[": ts.CharacterCodes.openBracket, - "'": ts.CharacterCodes.singleQuote, - '"': ts.CharacterCodes.doubleQuote, - "`": ts.CharacterCodes.backtick, - "<": ts.CharacterCodes.lessThan - }; + const openBraceMap: ts.StringMap = new ts.StringMap([ + ["(", ts.CharacterCodes.openParen], + ["{", ts.CharacterCodes.openBrace], + ["[", ts.CharacterCodes.openBracket], + ["'", ts.CharacterCodes.singleQuote], + ['"', ts.CharacterCodes.doubleQuote], + ["`", ts.CharacterCodes.backtick], + ["<", ts.CharacterCodes.lessThan] + ]); - const charCode = openBraceMap[openingBrace]; + const charCode = openBraceMap.get(openingBrace); if (!charCode) { this.raiseError(`Invalid openingBrace '${openingBrace}' specified.`); @@ -2239,8 +2240,7 @@ namespace FourSlash { public getMarkerByName(markerName: string) { const markerPos = this.testData.markerPositions[markerName]; if (markerPos === undefined) { - const markerNames: string[] = []; - for (const m in this.testData.markerPositions) markerNames.push(m); + const markerNames = Object.keys(this.testData.markerPositions); throw new Error(`Unknown marker "${markerName}" Available markers: ${markerNames.map(m => "\"" + m + "\"").join(", ")}`); } else { @@ -2346,7 +2346,7 @@ ${code} // List of all the subfiles we've parsed out const files: FourSlashFile[] = []; // Global options - const globalOptions: { [s: string]: string; } = {}; + const globalOptions: ts.Map = {}; // Marker positions // Split up the input file by line @@ -2361,7 +2361,7 @@ ${code} // Stuff related to the subfile we're parsing let currentFileContent: string = undefined; let currentFileName = fileName; - let currentFileOptions: { [s: string]: string } = {}; + let currentFileOptions: ts.Map = {}; function resetLocalData() { currentFileContent = undefined; @@ -2473,7 +2473,7 @@ ${code} return ts.forEach(files, f => getNonFileNameOptionInObject(f.fileOptions)); } - function getNonFileNameOptionInObject(optionObject: { [s: string]: string }): string { + function getNonFileNameOptionInObject(optionObject: ts.Map): string { for (const option in optionObject) { if (option !== metadataOptionNames.fileName) { return option; @@ -2772,7 +2772,7 @@ namespace FourSlashInterface { return this.state.getRanges(); } - public rangesByText(): ts.Map { + public rangesByText(): ts.StringMap { return this.state.rangesByText(); } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index f27e7e1c1749a..d145595058665 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -132,16 +132,16 @@ namespace Utils { } export function memoize(f: T): T { - const cache: { [idx: string]: any } = {}; + const cache = new ts.StringMap(); return (function(this: any) { const key = Array.prototype.join.call(arguments); - const cachedResult = cache[key]; + const cachedResult = cache.get(key); if (cachedResult) { return cachedResult; } else { - return cache[key] = f.apply(this, arguments); + return ts.setAndReturn(cache, key, f.apply(this, arguments)); } }); } @@ -848,19 +848,16 @@ namespace Harness { export const defaultLibFileName = "lib.d.ts"; export const es2015DefaultLibFileName = "lib.es2015.d.ts"; - const libFileNameSourceFileMap: ts.Map = { - [defaultLibFileName]: createSourceFileAndAssertInvariants(defaultLibFileName, IO.readFile(libFolder + "lib.es5.d.ts"), /*languageVersion*/ ts.ScriptTarget.Latest) - }; + const libFileNameSourceFileMap = ts.createStringMap( + defaultLibFileName, + createSourceFileAndAssertInvariants(defaultLibFileName, IO.readFile(libFolder + "lib.es5.d.ts"), /*languageVersion*/ ts.ScriptTarget.Latest)); export function getDefaultLibrarySourceFile(fileName = defaultLibFileName): ts.SourceFile { if (!isDefaultLibraryFile(fileName)) { return undefined; } - if (!libFileNameSourceFileMap[fileName]) { - libFileNameSourceFileMap[fileName] = createSourceFileAndAssertInvariants(fileName, IO.readFile(libFolder + fileName), ts.ScriptTarget.Latest); - } - return libFileNameSourceFileMap[fileName]; + return ts.getOrUpdate(libFileNameSourceFileMap, fileName, () => createSourceFileAndAssertInvariants(fileName, IO.readFile(libFolder + fileName), ts.ScriptTarget.Latest)); } export function getDefaultLibFileName(options: ts.CompilerOptions): string { @@ -1002,16 +999,13 @@ namespace Harness { { name: "symlink", type: "string" } ]; - let optionsIndex: ts.Map; + let optionsIndex: ts.StringMap; function getCommandLineOption(name: string): ts.CommandLineOption { if (!optionsIndex) { - optionsIndex = {}; const optionDeclarations = harnessOptionDeclarations.concat(ts.optionDeclarations); - for (const option of optionDeclarations) { - optionsIndex[option.name.toLowerCase()] = option; - } + optionsIndex = ts.createStringMapFromValues(optionDeclarations, option => option.name.toLowerCase()); } - return ts.lookUp(optionsIndex, name.toLowerCase()); + return optionsIndex.get(name.toLowerCase()); } export function setCompilerOptionsFromHarnessSetting(settings: Harness.TestCaseParser.CompilerSettings, options: ts.CompilerOptions & HarnessOptions): void { @@ -1602,14 +1596,14 @@ namespace Harness { } } - const fileCache: { [idx: string]: boolean } = {}; + const fileCache = new ts.StringSet(); function generateActual(actualFileName: string, generateContent: () => string): string { // For now this is written using TypeScript, because sys is not available when running old test cases. // But we need to move to sys once we have // Creates the directory including its parent if not already present function createDirectoryStructure(dirName: string) { - if (fileCache[dirName] || IO.directoryExists(dirName)) { - fileCache[dirName] = true; + if (fileCache.has(dirName) || IO.directoryExists(dirName)) { + fileCache.add(dirName); return; } @@ -1618,7 +1612,7 @@ namespace Harness { createDirectoryStructure(parentDirectory); } IO.createDirectory(dirName); - fileCache[dirName] = true; + fileCache.add(dirName); } // Create folders if needed diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index d7ed04b627f4f..0d22021ed2aa8 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -123,7 +123,7 @@ namespace Harness.LanguageService { } export class LanguageServiceAdapterHost { - protected fileNameToScript: ts.Map = {}; + protected fileNameToScript = new ts.StringMap(); constructor(protected cancellationToken = DefaultHostCancellationToken.Instance, protected settings = ts.getDefaultCompilerOptions()) { @@ -135,7 +135,7 @@ namespace Harness.LanguageService { public getFilenames(): string[] { const fileNames: string[] = []; - ts.forEachValue(this.fileNameToScript, (scriptInfo) => { + this.fileNameToScript.forEach(scriptInfo => { if (scriptInfo.isRootFile) { // only include root files here // usually it means that we won't include lib.d.ts in the list of root files so it won't mess the computation of compilation root dir. @@ -146,11 +146,11 @@ namespace Harness.LanguageService { } public getScriptInfo(fileName: string): ScriptInfo { - return ts.lookUp(this.fileNameToScript, fileName); + return this.fileNameToScript.get(fileName); } public addScript(fileName: string, content: string, isRootFile: boolean): void { - this.fileNameToScript[fileName] = new ScriptInfo(fileName, content, isRootFile); + this.fileNameToScript.set(fileName, new ScriptInfo(fileName, content, isRootFile)); } public editScript(fileName: string, start: number, end: number, newText: string) { @@ -171,7 +171,7 @@ namespace Harness.LanguageService { * @param col 0 based index */ public positionToLineAndCharacter(fileName: string, position: number): ts.LineAndCharacter { - const script: ScriptInfo = this.fileNameToScript[fileName]; + const script: ScriptInfo = this.fileNameToScript.get(fileName); assert.isOk(script); return ts.computeLineAndCharacterOfPosition(script.getLineMap(), position); diff --git a/src/harness/loggedIO.ts b/src/harness/loggedIO.ts index 33a46450a178a..400407d15a3e8 100644 --- a/src/harness/loggedIO.ts +++ b/src/harness/loggedIO.ts @@ -89,10 +89,10 @@ namespace Playback { } function memoize(func: (s: string) => T): Memoized { - let lookup: { [s: string]: T } = {}; + let lookup = new ts.StringMap(); const run: Memoized = >((s: string) => { - if (lookup.hasOwnProperty(s)) return lookup[s]; - return lookup[s] = func(s); + if (lookup.has(s)) return lookup.get(s); + return ts.setAndReturn(lookup, s, func(s)); }); run.reset = () => { lookup = undefined; @@ -128,9 +128,7 @@ namespace Playback { function initWrapper(wrapper: PlaybackSystem, underlying: ts.System): void; function initWrapper(wrapper: PlaybackIO, underlying: Harness.IO): void; function initWrapper(wrapper: PlaybackSystem | PlaybackIO, underlying: ts.System | Harness.IO): void { - ts.forEach(Object.keys(underlying), prop => { - (wrapper)[prop] = (underlying)[prop]; - }); + ts.copyMap(>underlying, wrapper); wrapper.startReplayFromString = logString => { wrapper.startReplayFromData(JSON.parse(logString)); diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index 038b2dbe35953..5ada2e0d8f82e 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -253,19 +253,21 @@ class ProjectRunner extends RunnerBase { moduleResolution: ts.ModuleResolutionKind.Classic, // currently all tests use classic module resolution kind, this will change in the future }; // Set the values specified using json - const optionNameMap: ts.Map = {}; - ts.forEach(ts.optionDeclarations, option => { - optionNameMap[option.name] = option; - }); + const optionNameMap = ts.createStringMapFromValues(ts.optionDeclarations, option => option.name); for (const name in testCase) { - if (name !== "mapRoot" && name !== "sourceRoot" && ts.hasProperty(optionNameMap, name)) { - const option = optionNameMap[name]; + if (name !== "mapRoot" && name !== "sourceRoot") { + const option = optionNameMap.get(name); + if (!option) { + continue; + } + const optType = option.type; let value = testCase[name]; if (typeof optType !== "string") { const key = value.toLowerCase(); - if (ts.hasProperty(optType, key)) { - value = optType[key]; + const mappedValue = optType.get(key); + if (mappedValue) { + value = mappedValue; } } compilerOptions[option.name] = value; diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 3b9025c27c362..58f3cba1e2e4e 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -9,7 +9,7 @@ "declaration": false, "stripInternal": true, "types": [ - "node", "mocha", "chai" + "node", "mocha", "chai", "../compiler/types.ts" ] }, "files": [ diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index e0ce09391fb86..3ab3f18964757 100644 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ b/src/harness/unittests/cachingInServerLSHost.ts @@ -6,13 +6,13 @@ namespace ts { content: string; } - function createDefaultServerHost(fileMap: Map): server.ServerHost { - const existingDirectories: Map = {}; - forEachValue(fileMap, v => { + function createDefaultServerHost(fileMap: ts.StringMap): server.ServerHost { + const existingDirectories = new StringSet(); + fileMap.forEach(v => { let dir = getDirectoryPath(v.name); let previous: string; do { - existingDirectories[dir] = true; + existingDirectories.add(dir); previous = dir; dir = getDirectoryPath(dir); } while (dir !== previous); @@ -24,7 +24,8 @@ namespace ts { write: (s: string) => { }, readFile: (path: string, encoding?: string): string => { - return hasProperty(fileMap, path) && fileMap[path].content; + const file = fileMap.get(path); + return file && file.content; }, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => { throw new Error("NYI"); @@ -33,10 +34,10 @@ namespace ts { throw new Error("NYI"); }, fileExists: (path: string): boolean => { - return hasProperty(fileMap, path); + return fileMap.has(path); }, directoryExists: (path: string): boolean => { - return hasProperty(existingDirectories, path); + return existingDirectories.has(path); }, createDirectory: (path: string) => { }, @@ -101,7 +102,7 @@ namespace ts { content: `foo()` }; - const serverHost = createDefaultServerHost({ [root.name]: root, [imported.name]: imported }); + const serverHost = createDefaultServerHost(new StringMap([[root.name, root], [imported.name, imported]])); const { project, rootScriptInfo } = createProject(root.name, serverHost); // ensure that imported file was found @@ -193,7 +194,7 @@ namespace ts { content: `export var y = 1` }; - const fileMap: Map = { [root.name]: root }; + const fileMap = createStringMap(root.name, root); const serverHost = createDefaultServerHost(fileMap); const originalFileExists = serverHost.fileExists; @@ -217,7 +218,7 @@ namespace ts { assert.isTrue(typeof diags[0].messageText === "string" && ((diags[0].messageText).indexOf("Cannot find module") === 0), "should be 'cannot find module' message"); // assert that import will success once file appear on disk - fileMap[imported.name] = imported; + fileMap.set(imported.name, imported); fileExistsCalledForBar = false; rootScriptInfo.editContent(0, content.length, `import {y} from "bar"`); diff --git a/src/harness/unittests/matchFiles.ts b/src/harness/unittests/matchFiles.ts index 6b499e5698909..d2f5da0b282dc 100644 --- a/src/harness/unittests/matchFiles.ts +++ b/src/harness/unittests/matchFiles.ts @@ -418,9 +418,7 @@ namespace ts { "c:/dev/b.ts", "c:/dev/c.d.ts" ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.None - }, + wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.None }, }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -441,9 +439,7 @@ namespace ts { "c:/dev/b.ts", "c:/dev/c.d.ts" ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.None - }, + wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.None }, }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -463,9 +459,7 @@ namespace ts { "c:/dev/x/a.ts", "c:/dev/x/b.ts" ], - wildcardDirectories: { - "c:/dev/x": ts.WatchDirectoryFlags.None - }, + wildcardDirectories: { "c:/dev/x": ts.WatchDirectoryFlags.None }, }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -487,9 +481,7 @@ namespace ts { "c:/dev/x/y/a.ts", "c:/dev/z/a.ts" ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.Recursive - }, + wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -534,9 +526,7 @@ namespace ts { fileNames: [ "/dev/A.ts" ], - wildcardDirectories: { - "/dev": ts.WatchDirectoryFlags.Recursive - }, + wildcardDirectories: { "/dev": ts.WatchDirectoryFlags.Recursive }, }; const actual = ts.parseJsonConfigFileContent(json, caseSensitiveHost, caseSensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -553,9 +543,7 @@ namespace ts { options: {}, errors: [], fileNames: [], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.Recursive - }, + wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -580,9 +568,7 @@ namespace ts { fileNames: [ "c:/dev/a.ts" ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.Recursive - }, + wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -607,9 +593,7 @@ namespace ts { "c:/dev/b.ts", "c:/dev/c.d.ts" ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.Recursive - } + wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -628,9 +612,7 @@ namespace ts { fileNames: [ "c:/dev/a.ts" ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.Recursive - }, + wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -654,9 +636,7 @@ namespace ts { "c:/dev/jspm_packages/a.ts", "c:/dev/node_modules/a.ts" ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.Recursive - }, + wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -679,9 +659,7 @@ namespace ts { "c:/dev/jspm_packages/a.ts", "c:/dev/node_modules/a.ts" ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.Recursive - }, + wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -703,9 +681,7 @@ namespace ts { }, errors: [], fileNames: [], - wildcardDirectories: { - "c:/dev/js": ts.WatchDirectoryFlags.None - } + wildcardDirectories: { "c:/dev/js": ts.WatchDirectoryFlags.None } }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -730,9 +706,7 @@ namespace ts { "c:/dev/js/a.js", "c:/dev/js/b.js" ], - wildcardDirectories: { - "c:/dev/js": ts.WatchDirectoryFlags.None - } + wildcardDirectories: { "c:/dev/js": ts.WatchDirectoryFlags.None } }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -757,9 +731,7 @@ namespace ts { "c:/dev/js/ab.min.js", "c:/dev/js/d.min.js" ], - wildcardDirectories: { - "c:/dev/js": ts.WatchDirectoryFlags.None - } + wildcardDirectories: { "c:/dev/js": ts.WatchDirectoryFlags.None } }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -808,9 +780,7 @@ namespace ts { fileNames: [ "c:/ext/ext.ts" ], - wildcardDirectories: { - "c:/ext": ts.WatchDirectoryFlags.None - } + wildcardDirectories: { "c:/ext": ts.WatchDirectoryFlags.None } }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -874,9 +844,7 @@ namespace ts { fileNames: [ "c:/ext/ext.ts", ], - wildcardDirectories: { - "c:/ext": ts.WatchDirectoryFlags.Recursive - } + wildcardDirectories: { "c:/ext": ts.WatchDirectoryFlags.Recursive } }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -899,9 +867,7 @@ namespace ts { "c:/dev/b.tsx", "c:/dev/c.tsx", ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.Recursive - } + wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -926,9 +892,7 @@ namespace ts { "c:/dev/b.tsx", "c:/dev/c.tsx", ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.Recursive - } + wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -953,9 +917,7 @@ namespace ts { "c:/dev/d.js", "c:/dev/e.jsx", ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.Recursive - } + wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -982,9 +944,7 @@ namespace ts { "c:/dev/d.js", "c:/dev/e.jsx", ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.Recursive - } + wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -1011,9 +971,7 @@ namespace ts { fileNames: [ "c:/dev/js/d.min.js" ], - wildcardDirectories: { - "c:/dev/js": ts.WatchDirectoryFlags.None - } + wildcardDirectories: { "c:/dev/js": ts.WatchDirectoryFlags.None } }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -1101,9 +1059,7 @@ namespace ts { "c:/dev/x/y/a.ts", "c:/dev/z/a.ts" ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.Recursive - } + wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -1173,9 +1129,7 @@ namespace ts { "c:/dev/x/y/a.ts", "c:/dev/z/a.ts" ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.Recursive - } + wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -1203,9 +1157,7 @@ namespace ts { "c:/dev/x/y/a.ts", "c:/dev/z/a.ts" ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.Recursive - } + wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); @@ -1276,9 +1228,7 @@ namespace ts { "c:/dev/w/.u/e.ts", "c:/dev/x/.y/a.ts" ], - wildcardDirectories: { - "c:/dev": ts.WatchDirectoryFlags.Recursive - } + wildcardDirectories: { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); assert.deepEqual(actual.fileNames, expected.fileNames); diff --git a/src/harness/unittests/moduleResolution.ts b/src/harness/unittests/moduleResolution.ts index 5f63889b08498..d9e94bfab6c2c 100644 --- a/src/harness/unittests/moduleResolution.ts +++ b/src/harness/unittests/moduleResolution.ts @@ -7,14 +7,14 @@ namespace ts { } function createModuleResolutionHost(hasDirectoryExists: boolean, ...files: File[]): ModuleResolutionHost { - const map = arrayToMap(files, f => f.name); + const map = createStringMapFromValues(files, f => f.name); if (hasDirectoryExists) { - const directories: Map = {}; + const directories = new StringSet(); for (const f of files) { let name = getDirectoryPath(f.name); while (true) { - directories[name] = name; + directories.add(name); const baseName = getDirectoryPath(name); if (baseName === name) { break; @@ -24,20 +24,19 @@ namespace ts { } return { readFile, - directoryExists: path => { - return hasProperty(directories, path); - }, + directoryExists: path => directories.has(path), fileExists: path => { - assert.isTrue(hasProperty(directories, getDirectoryPath(path)), `'fileExists' '${path}' request in non-existing directory`); - return hasProperty(map, path); + assert.isTrue(directories.has(getDirectoryPath(path)), `'fileExists' '${path}' request in non-existing directory`); + return map.has(path); } }; } else { - return { readFile, fileExists: path => hasProperty(map, path), }; + return { readFile, fileExists: path => map.has(path), }; } function readFile(path: string): string { - return hasProperty(map, path) ? map[path].content : undefined; + const file = map.get(path); + return file ? file.content : undefined; } } @@ -282,12 +281,13 @@ namespace ts { }); describe("Module resolution - relative imports", () => { - function test(files: Map, currentDirectory: string, rootFiles: string[], expectedFilesCount: number, relativeNamesToCheck: string[]) { + function test(files: ts.StringMap, currentDirectory: string, rootFiles: string[], expectedFilesCount: number, relativeNamesToCheck: string[]) { const options: CompilerOptions = { module: ModuleKind.CommonJS }; const host: CompilerHost = { getSourceFile: (fileName: string, languageVersion: ScriptTarget) => { const path = normalizePath(combinePaths(currentDirectory, fileName)); - return hasProperty(files, path) ? createSourceFile(fileName, files[path], languageVersion) : undefined; + const file = files.get(path); + return file ? createSourceFile(fileName, file, languageVersion) : undefined; }, getDefaultLibFileName: () => "lib.d.ts", writeFile: (fileName, content): void => { throw new Error("NotImplemented"); }, @@ -298,7 +298,7 @@ namespace ts { useCaseSensitiveFileNames: () => false, fileExists: fileName => { const path = normalizePath(combinePaths(currentDirectory, fileName)); - return hasProperty(files, path); + return files.has(path); }, readFile: (fileName): string => { throw new Error("NotImplemented"); } }; @@ -318,51 +318,47 @@ namespace ts { } it("should find all modules", () => { - const files: Map = { - "/a/b/c/first/shared.ts": ` + const files = new StringMap([ + ["/a/b/c/first/shared.ts", ` class A {} -export = A`, - "/a/b/c/first/second/class_a.ts": ` +export = A`], + ["/a/b/c/first/second/class_a.ts", ` import Shared = require('../shared'); import C = require('../../third/class_c'); class B {} -export = B;`, - "/a/b/c/third/class_c.ts": ` +export = B;`], + ["/a/b/c/third/class_c.ts", ` import Shared = require('../first/shared'); class C {} export = C; - ` - }; + `] + ]); test(files, "/a/b/c/first/second", ["class_a.ts"], 3, ["../../../c/third/class_c.ts"]); }); it("should find modules in node_modules", () => { - const files: Map = { - "/parent/node_modules/mod/index.d.ts": "export var x", - "/parent/app/myapp.ts": `import {x} from "mod"` - }; + const files = new StringMap([ + ["/parent/node_modules/mod/index.d.ts", "export var x"], + ["/parent/app/myapp.ts", `import {x} from "mod"`] + ]); test(files, "/parent/app", ["myapp.ts"], 2, []); }); it("should find file referenced via absolute and relative names", () => { - const files: Map = { - "/a/b/c.ts": `/// `, - "/a/b/b.ts": "var x" - }; + const files = new StringMap([ + ["/a/b/c.ts", `/// `], + ["/a/b/b.ts", "var x"] + ]); test(files, "/a/b", ["c.ts", "/a/b/b.ts"], 2, []); }); }); describe("Files with different casing", () => { const library = createSourceFile("lib.d.ts", "", ScriptTarget.ES5); - function test(files: Map, options: CompilerOptions, currentDirectory: string, useCaseSensitiveFileNames: boolean, rootFiles: string[], diagnosticCodes: number[]): void { + function test(files: ts.StringMap, options: CompilerOptions, currentDirectory: string, useCaseSensitiveFileNames: boolean, rootFiles: string[], diagnosticCodes: number[]): void { const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); if (!useCaseSensitiveFileNames) { - const f: Map = {}; - for (const fileName in files) { - f[getCanonicalFileName(fileName)] = files[fileName]; - } - files = f; + files = stringMapKeys(files, getCanonicalFileName); } const host: CompilerHost = { @@ -371,7 +367,8 @@ export = C; return library; } const path = getCanonicalFileName(normalizePath(combinePaths(currentDirectory, fileName))); - return hasProperty(files, path) ? createSourceFile(fileName, files[path], languageVersion) : undefined; + const file = files.get(path); + return file ? createSourceFile(fileName, file, languageVersion) : undefined; }, getDefaultLibFileName: () => "lib.d.ts", writeFile: (fileName, content): void => { throw new Error("NotImplemented"); }, @@ -382,7 +379,7 @@ export = C; useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, fileExists: fileName => { const path = getCanonicalFileName(normalizePath(combinePaths(currentDirectory, fileName))); - return hasProperty(files, path); + return files.has(path); }, readFile: (fileName): string => { throw new Error("NotImplemented"); } }; @@ -395,77 +392,77 @@ export = C; } it("should succeed when the same file is referenced using absolute and relative names", () => { - const files: Map = { - "/a/b/c.ts": `/// `, - "/a/b/d.ts": "var x" - }; + const files = new StringMap([ + ["/a/b/c.ts", `/// `], + ["/a/b/d.ts", "var x"] + ]); test(files, { module: ts.ModuleKind.AMD }, "/a/b", /*useCaseSensitiveFileNames*/ false, ["c.ts", "/a/b/d.ts"], []); }); it("should fail when two files used in program differ only in casing (tripleslash references)", () => { - const files: Map = { - "/a/b/c.ts": `/// `, - "/a/b/d.ts": "var x" - }; + const files = new StringMap([ + ["/a/b/c.ts", `/// `], + ["/a/b/d.ts", "var x"] + ]); test(files, { module: ts.ModuleKind.AMD, forceConsistentCasingInFileNames: true }, "/a/b", /*useCaseSensitiveFileNames*/ false, ["c.ts", "d.ts"], [1149]); }); it("should fail when two files used in program differ only in casing (imports)", () => { - const files: Map = { - "/a/b/c.ts": `import {x} from "D"`, - "/a/b/d.ts": "export var x" - }; + const files = new StringMap([ + ["/a/b/c.ts", `import {x} from "D"`], + ["/a/b/d.ts", "export var x"] + ]); test(files, { module: ts.ModuleKind.AMD, forceConsistentCasingInFileNames: true }, "/a/b", /*useCaseSensitiveFileNames*/ false, ["c.ts", "d.ts"], [1149]); }); it("should fail when two files used in program differ only in casing (imports, relative module names)", () => { - const files: Map = { - "moduleA.ts": `import {x} from "./ModuleB"`, - "moduleB.ts": "export var x" - }; + const files = new StringMap([ + ["moduleA.ts", `import {x} from "./ModuleB"`], + ["moduleB.ts", "export var x"] + ]); test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "", /*useCaseSensitiveFileNames*/ false, ["moduleA.ts", "moduleB.ts"], [1149]); }); it("should fail when two files exist on disk that differs only in casing", () => { - const files: Map = { - "/a/b/c.ts": `import {x} from "D"`, - "/a/b/D.ts": "export var x", - "/a/b/d.ts": "export var y" - }; + const files = new StringMap([ + ["/a/b/c.ts", `import {x} from "D"`], + ["/a/b/D.ts", "export var x"], + ["/a/b/d.ts", "export var y"] + ]); test(files, { module: ts.ModuleKind.AMD }, "/a/b", /*useCaseSensitiveFileNames*/ true, ["c.ts", "d.ts"], [1149]); }); it("should fail when module name in 'require' calls has inconsistent casing", () => { - const files: Map = { - "moduleA.ts": `import a = require("./ModuleC")`, - "moduleB.ts": `import a = require("./moduleC")`, - "moduleC.ts": "export var x" - }; + const files = new StringMap([ + ["moduleA.ts", `import a = require("./ModuleC")`], + ["moduleB.ts", `import a = require("./moduleC")`], + ["moduleC.ts", "export var x"] + ]); test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "", /*useCaseSensitiveFileNames*/ false, ["moduleA.ts", "moduleB.ts", "moduleC.ts"], [1149, 1149]); }); it("should fail when module names in 'require' calls has inconsistent casing and current directory has uppercase chars", () => { - const files: Map = { - "/a/B/c/moduleA.ts": `import a = require("./ModuleC")`, - "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, - "/a/B/c/moduleC.ts": "export var x", - "/a/B/c/moduleD.ts": ` + const files = new StringMap([ + ["/a/B/c/moduleA.ts", `import a = require("./ModuleC")`], + ["/a/B/c/moduleB.ts", `import a = require("./moduleC")`], + ["/a/B/c/moduleC.ts", "export var x"], + ["/a/B/c/moduleD.ts", ` import a = require("./moduleA.ts"); import b = require("./moduleB.ts"); - ` - }; + `] + ]); test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "/a/B/c", /*useCaseSensitiveFileNames*/ false, ["moduleD.ts"], [1149]); }); it("should not fail when module names in 'require' calls has consistent casing and current directory has uppercase chars", () => { - const files: Map = { - "/a/B/c/moduleA.ts": `import a = require("./moduleC")`, - "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, - "/a/B/c/moduleC.ts": "export var x", - "/a/B/c/moduleD.ts": ` + const files = new StringMap([ + ["/a/B/c/moduleA.ts", `import a = require("./moduleC")`], + ["/a/B/c/moduleB.ts", `import a = require("./moduleC")`], + ["/a/B/c/moduleC.ts", "export var x"], + ["/a/B/c/moduleD.ts", ` import a = require("./moduleA.ts"); import b = require("./moduleB.ts"); - ` - }; + `] + ]); test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "/a/B/c", /*useCaseSensitiveFileNames*/ false, ["moduleD.ts"], []); }); }); @@ -1021,10 +1018,10 @@ import b = require("./moduleB.ts"); const files = [f1, f2, f3, f4]; const names = map(files, f => f.name); - const sourceFiles = arrayToMap(map(files, f => createSourceFile(f.name, f.content, ScriptTarget.ES6)), f => f.fileName); + const sourceFiles = createStringMapFromArray(files, f => f.name, f => createSourceFile(f.name, f.content, ScriptTarget.ES6)); const compilerHost: CompilerHost = { - fileExists : fileName => hasProperty(sourceFiles, fileName), - getSourceFile: fileName => sourceFiles[fileName], + fileExists : fileName => sourceFiles.has(fileName), + getSourceFile: fileName => sourceFiles.get(fileName), getDefaultLibFileName: () => "lib.d.ts", writeFile(file, text) { throw new Error("NYI"); @@ -1034,7 +1031,7 @@ import b = require("./moduleB.ts"); getCanonicalFileName: f => f.toLowerCase(), getNewLine: () => "\r\n", useCaseSensitiveFileNames: () => false, - readFile: fileName => hasProperty(sourceFiles, fileName) ? sourceFiles[fileName].text : undefined + readFile: fileName => sourceFiles.has(fileName) ? sourceFiles.get(fileName).text : undefined }; const program1 = createProgram(names, {}, compilerHost); const diagnostics1 = program1.getFileProcessingDiagnostics().getDiagnostics(); diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index 8b2b15c15b30d..af61bcc0bb4df 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -96,16 +96,16 @@ namespace ts { } function createTestCompilerHost(texts: NamedSourceText[], target: ScriptTarget): CompilerHost { - const files: Map = {}; + const files = new ts.StringMap(); for (const t of texts) { const file = createSourceFile(t.name, t.text.getFullText(), target); file.sourceText = t.text; - files[t.name] = file; + files.set(t.name, file); } return { getSourceFile(fileName): SourceFile { - return files[fileName]; + return files.get(fileName); }, getDefaultLibFileName(): string { return "lib.d.ts"; @@ -128,9 +128,9 @@ namespace ts { getNewLine(): string { return sys ? sys.newLine : newLine; }, - fileExists: fileName => hasProperty(files, fileName), + fileExists: fileName => files.has(fileName), readFile: fileName => { - const file = lookUp(files, fileName); + const file = files.get(fileName); return file && file.text; } }; @@ -152,16 +152,6 @@ namespace ts { return program; } - function getSizeOfMap(map: Map): number { - let size = 0; - for (const id in map) { - if (hasProperty(map, id)) { - size++; - } - } - return size; - } - function checkResolvedModule(expected: ResolvedModule, actual: ResolvedModule): void { assert.isTrue(actual !== undefined); assert.isTrue(expected.resolvedFileName === actual.resolvedFileName, `'resolvedFileName': expected '${expected.resolvedFileName}' to be equal to '${actual.resolvedFileName}'`); @@ -174,7 +164,7 @@ namespace ts { assert.isTrue(expected.primary === actual.primary, `'primary': expected '${expected.primary}' to be equal to '${actual.primary}'`); } - function checkCache(caption: string, program: Program, fileName: string, expectedContent: Map, getCache: (f: SourceFile) => Map, entryChecker: (expected: T, original: T) => void): void { + function checkCache(caption: string, program: Program, fileName: string, expectedContent: ts.StringMap, getCache: (f: SourceFile) => ts.StringMap, entryChecker: (expected: T, original: T) => void): void { const file = program.getSourceFile(fileName); assert.isTrue(file !== undefined, `cannot find file ${fileName}`); const cache = getCache(file); @@ -183,31 +173,27 @@ namespace ts { } else { assert.isTrue(cache !== undefined, `expected ${caption} to be set`); - const actualCacheSize = getSizeOfMap(cache); - const expectedSize = getSizeOfMap(expectedContent); + const actualCacheSize = ts.stringMapSize(cache); + const expectedSize = ts.stringMapSize(expectedContent); assert.isTrue(actualCacheSize === expectedSize, `expected actual size: ${actualCacheSize} to be equal to ${expectedSize}`); - for (const id in expectedContent) { - if (hasProperty(expectedContent, id)) { - - if (expectedContent[id]) { - const expected = expectedContent[id]; - const actual = cache[id]; - entryChecker(expected, actual); - } + expectedContent.forEach((expected, id) => { + if (expected) { + const actual = cache.get(id); + entryChecker(expected, actual); } else { - assert.isTrue(cache[id] === undefined); + assert.isTrue(cache.get(id) === undefined); } - } + }); } } - function checkResolvedModulesCache(program: Program, fileName: string, expectedContent: Map): void { + function checkResolvedModulesCache(program: Program, fileName: string, expectedContent: ts.StringMap): void { checkCache("resolved modules", program, fileName, expectedContent, f => f.resolvedModules, checkResolvedModule); } - function checkResolvedTypeDirectivesCache(program: Program, fileName: string, expectedContent: Map): void { + function checkResolvedTypeDirectivesCache(program: Program, fileName: string, expectedContent: ts.StringMap): void { checkCache("resolved type directives", program, fileName, expectedContent, f => f.resolvedTypeReferenceDirectiveNames, checkResolvedTypeDirective); } @@ -320,7 +306,7 @@ namespace ts { const options: CompilerOptions = { target }; const program_1 = newProgram(files, ["a.ts"], options); - checkResolvedModulesCache(program_1, "a.ts", { "b": { resolvedFileName: "b.ts" } }); + checkResolvedModulesCache(program_1, "a.ts", createStringMap("b", { resolvedFileName: "b.ts" })); checkResolvedModulesCache(program_1, "b.ts", undefined); const program_2 = updateProgram(program_1, ["a.ts"], options, files => { @@ -329,7 +315,7 @@ namespace ts { assert.isTrue(program_1.structureIsReused); // content of resolution cache should not change - checkResolvedModulesCache(program_1, "a.ts", { "b": { resolvedFileName: "b.ts" } }); + checkResolvedModulesCache(program_1, "a.ts", createStringMap("b", { resolvedFileName: "b.ts" })); checkResolvedModulesCache(program_1, "b.ts", undefined); // imports has changed - program is not reused @@ -346,7 +332,7 @@ namespace ts { files[0].text = files[0].text.updateImportsAndExports(newImports); }); assert.isTrue(!program_3.structureIsReused); - checkResolvedModulesCache(program_4, "a.ts", { "b": { resolvedFileName: "b.ts" }, "c": undefined }); + checkResolvedModulesCache(program_4, "a.ts", new StringMap([["b", { resolvedFileName: "b.ts" }], ["c", undefined]])); }); it("resolved type directives cache follows type directives", () => { @@ -357,7 +343,7 @@ namespace ts { const options: CompilerOptions = { target, typeRoots: ["/types"] }; const program_1 = newProgram(files, ["/a.ts"], options); - checkResolvedTypeDirectivesCache(program_1, "/a.ts", { "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }); + checkResolvedTypeDirectivesCache(program_1, "/a.ts", createStringMap("typedefs", { resolvedFileName: "/types/typedefs/index.d.ts", primary: true })); checkResolvedTypeDirectivesCache(program_1, "/types/typedefs/index.d.ts", undefined); const program_2 = updateProgram(program_1, ["/a.ts"], options, files => { @@ -366,7 +352,7 @@ namespace ts { assert.isTrue(program_1.structureIsReused); // content of resolution cache should not change - checkResolvedTypeDirectivesCache(program_1, "/a.ts", { "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }); + checkResolvedTypeDirectivesCache(program_1, "/a.ts", createStringMap("typedefs", { resolvedFileName: "/types/typedefs/index.d.ts", primary: true })); checkResolvedTypeDirectivesCache(program_1, "/types/typedefs/index.d.ts", undefined); // type reference directives has changed - program is not reused @@ -384,7 +370,7 @@ namespace ts { files[0].text = files[0].text.updateReferences(newReferences); }); assert.isTrue(!program_3.structureIsReused); - checkResolvedTypeDirectivesCache(program_1, "/a.ts", { "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }); + checkResolvedTypeDirectivesCache(program_1, "/a.ts", createStringMap("typedefs", { resolvedFileName: "/types/typedefs/index.d.ts", primary: true })); }); }); diff --git a/src/harness/unittests/session.ts b/src/harness/unittests/session.ts index c528554432914..e10dd38718f28 100644 --- a/src/harness/unittests/session.ts +++ b/src/harness/unittests/session.ts @@ -362,15 +362,16 @@ namespace ts.server { class InProcClient { private server: InProcSession; private seq = 0; - private callbacks: ts.Map<(resp: protocol.Response) => void> = {}; - private eventHandlers: ts.Map<(args: any) => void> = {}; + private callbacks = new NumberMap<(resp: protocol.Response) => void>(); + private eventHandlers = new StringMap<(args: any) => void>(); handle(msg: protocol.Message): void { if (msg.type === "response") { const response = msg; - if (this.callbacks[response.request_seq]) { - this.callbacks[response.request_seq](response); - delete this.callbacks[response.request_seq]; + const callback = this.callbacks.get(response.request_seq); + if (callback) { + callback(response); + this.callbacks.delete(response.request_seq); } } else if (msg.type === "event") { @@ -380,13 +381,14 @@ namespace ts.server { } emit(name: string, args: any): void { - if (this.eventHandlers[name]) { - this.eventHandlers[name](args); + const handler = this.eventHandlers.get(name); + if (handler) { + handler(args); } } on(name: string, handler: (args: any) => void): void { - this.eventHandlers[name] = handler; + this.eventHandlers.set(name, handler); } connect(session: InProcSession): void { @@ -404,7 +406,7 @@ namespace ts.server { command, arguments: args }); - this.callbacks[this.seq] = callback; + this.callbacks.set(this.seq, callback); } }; diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 7a375c50b8a36..c4e35761cdabe 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -68,20 +68,10 @@ namespace ts { return entry; } - function sizeOfMap(map: Map): number { - let n = 0; - for (const name in map) { - if (hasProperty(map, name)) { - n++; - } - } - return n; - } - - function checkMapKeys(caption: string, map: Map, expectedKeys: string[]) { - assert.equal(sizeOfMap(map), expectedKeys.length, `${caption}: incorrect size of map`); + function checkMapKeys(caption: string, map: StringMap, expectedKeys: string[]) { + assert.equal(stringMapSize(map), expectedKeys.length, `${caption}: incorrect size of map`); for (const name of expectedKeys) { - assert.isTrue(hasProperty(map, name), `${caption} is expected to contain ${name}, actual keys: ${getKeys(map)}`); + assert.isTrue(map.has(name), `${caption} is expected to contain ${name}, actual keys: ${ts.keysArray(map)}`); } } @@ -126,8 +116,8 @@ namespace ts { private getCanonicalFileName: (s: string) => string; private toPath: (f: string) => Path; private callbackQueue: TimeOutCallback[] = []; - readonly watchedDirectories: Map<{ cb: DirectoryWatcherCallback, recursive: boolean }[]> = {}; - readonly watchedFiles: Map = {}; + readonly watchedDirectories = new StringMap<{ cb: DirectoryWatcherCallback, recursive: boolean }[]>(); + readonly watchedFiles = new StringMap(); constructor(public useCaseSensitiveFileNames: boolean, private executingFilePath: string, private currentDirectory: string, fileOrFolderList: FileOrFolder[]) { this.getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); @@ -208,8 +198,7 @@ namespace ts { watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean): DirectoryWatcher { const path = this.toPath(directoryName); - const callbacks = lookUp(this.watchedDirectories, path) || (this.watchedDirectories[path] = []); - callbacks.push({ cb: callback, recursive }); + const callbacks = multiMapAdd(this.watchedDirectories, path, { cb: callback, recursive }); return { referenceCount: 0, directoryName, @@ -221,7 +210,7 @@ namespace ts { } } if (!callbacks.length) { - delete this.watchedDirectories[path]; + this.watchedDirectories.delete(path); } } }; @@ -229,7 +218,7 @@ namespace ts { triggerDirectoryWatcherCallback(directoryName: string, fileName: string): void { const path = this.toPath(directoryName); - const callbacks = lookUp(this.watchedDirectories, path); + const callbacks = this.watchedDirectories.get(path); if (callbacks) { for (const callback of callbacks) { callback.cb(fileName); @@ -239,7 +228,7 @@ namespace ts { triggerFileWatcherCallback(fileName: string, removed?: boolean): void { const path = this.toPath(fileName); - const callbacks = lookUp(this.watchedFiles, path); + const callbacks = this.watchedFiles.get(path); if (callbacks) { for (const callback of callbacks) { callback(path, removed); @@ -249,14 +238,13 @@ namespace ts { watchFile(fileName: string, callback: FileWatcherCallback) { const path = this.toPath(fileName); - const callbacks = lookUp(this.watchedFiles, path) || (this.watchedFiles[path] = []); - callbacks.push(callback); + const callbacks = multiMapAdd(this.watchedFiles, path, callback); return { close: () => { const i = callbacks.indexOf(callback); callbacks.splice(i, 1); if (!callbacks.length) { - delete this.watchedFiles[path]; + this.watchedFiles.delete(path); } } }; @@ -594,7 +582,7 @@ namespace ts { content: `{ "compilerOptions": { "target": "es6" - }, + }, "files": [ "main.ts" ] }` }; @@ -621,7 +609,7 @@ namespace ts { content: `{ "compilerOptions": { "target": "es6" - }, + }, "files": [ "main.ts" ] }` }; diff --git a/src/server/client.ts b/src/server/client.ts index f04dbd8dc0253..acd470c828b69 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -21,7 +21,7 @@ namespace ts.server { export class SessionClient implements LanguageService { private sequence: number = 0; - private lineMaps: ts.Map = {}; + private lineMaps = new ts.StringMap(); private messages: string[] = []; private lastRenameEntry: RenameEntry; @@ -37,12 +37,10 @@ namespace ts.server { } private getLineMap(fileName: string): number[] { - let lineMap = ts.lookUp(this.lineMaps, fileName); - if (!lineMap) { + return ts.getOrUpdate(this.lineMaps, fileName, () => { const scriptSnapshot = this.host.getScriptSnapshot(fileName); - lineMap = this.lineMaps[fileName] = ts.computeLineStarts(scriptSnapshot.getText(0, scriptSnapshot.getLength())); - } - return lineMap; + return ts.computeLineStarts(scriptSnapshot.getText(0, scriptSnapshot.getLength())); + }); } private lineOffsetToPosition(fileName: string, lineOffset: protocol.Location, lineMap?: number[]): number { @@ -146,7 +144,7 @@ namespace ts.server { changeFile(fileName: string, start: number, end: number, newText: string): void { // clear the line map after an edit - this.lineMaps[fileName] = undefined; + this.lineMaps.delete(fileName); const lineOffset = this.positionToOneBasedLineOffset(fileName, start); const endLineOffset = this.positionToOneBasedLineOffset(fileName, end); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 6e9adad7f472f..2758bcc80eeb0 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -18,10 +18,9 @@ namespace ts.server { const lineCollectionCapacity = 4; function mergeFormatOptions(formatCodeOptions: FormatCodeOptions, formatOptions: protocol.FormatOptions): void { - const hasOwnProperty = Object.prototype.hasOwnProperty; - Object.keys(formatOptions).forEach((key) => { + forEachKey(formatOptions, key => { const codeKey = key.charAt(0).toUpperCase() + key.substring(1); - if (hasOwnProperty.call(formatCodeOptions, codeKey)) { + if (hasProperty(formatCodeOptions, codeKey)) { formatCodeOptions[codeKey] = formatOptions[key]; } }); @@ -100,15 +99,15 @@ namespace ts.server { filenameToScript: ts.FileMap; roots: ScriptInfo[] = []; - private resolvedModuleNames: ts.FileMap>; - private resolvedTypeReferenceDirectives: ts.FileMap>; + private resolvedModuleNames: ts.FileMap>; + private resolvedTypeReferenceDirectives: ts.FileMap>; private moduleResolutionHost: ts.ModuleResolutionHost; private getCanonicalFileName: (fileName: string) => string; constructor(public host: ServerHost, public project: Project) { this.getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); - this.resolvedModuleNames = createFileMap>(); - this.resolvedTypeReferenceDirectives = createFileMap>(); + this.resolvedModuleNames = createFileMap>(); + this.resolvedTypeReferenceDirectives = createFileMap>(); this.filenameToScript = createFileMap(); this.moduleResolutionHost = { fileExists: fileName => this.fileExists(fileName), @@ -123,22 +122,22 @@ namespace ts.server { private resolveNamesWithLocalCache( names: string[], containingFile: string, - cache: ts.FileMap>, + cache: ts.FileMap>, loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T, getResult: (s: T) => R): R[] { const path = toPath(containingFile, this.host.getCurrentDirectory(), this.getCanonicalFileName); const currentResolutionsInFile = cache.get(path); - const newResolutions: Map = {}; + const newResolutions = new ts.StringMap(); const resolvedModules: R[] = []; const compilerOptions = this.getCompilationSettings(); for (const name of names) { // check if this is a duplicate entry in the list - let resolution = lookUp(newResolutions, name); + let resolution = newResolutions.get(name); if (!resolution) { - const existingResolution = currentResolutionsInFile && ts.lookUp(currentResolutionsInFile, name); + const existingResolution = currentResolutionsInFile && currentResolutionsInFile.get(name); if (moduleResolutionIsValid(existingResolution)) { // ok, it is safe to use existing name resolution results resolution = existingResolution; @@ -146,7 +145,7 @@ namespace ts.server { else { resolution = loader(name, containingFile, compilerOptions, this.moduleResolutionHost); resolution.lastCheckTime = Date.now(); - newResolutions[name] = resolution; + newResolutions.set(name, resolution); } } @@ -378,7 +377,7 @@ namespace ts.server { export interface ProjectOptions { // these fields can be present in the project file files?: string[]; - wildcardDirectories?: ts.Map; + wildcardDirectories?: Map; compilerOptions?: ts.CompilerOptions; } @@ -1124,7 +1123,7 @@ namespace ts.server { getScriptInfo(filename: string) { filename = ts.normalizePath(filename); - return ts.lookUp(this.filenameToScriptInfo, filename); + return ts.getProperty(this.filenameToScriptInfo, filename); } /** @@ -1133,7 +1132,7 @@ namespace ts.server { */ openFile(fileName: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind) { fileName = ts.normalizePath(fileName); - let info = ts.lookUp(this.filenameToScriptInfo, fileName); + let info = ts.getProperty(this.filenameToScriptInfo, fileName); if (!info) { let content: string; if (this.host.fileExists(fileName)) { @@ -1246,7 +1245,7 @@ namespace ts.server { * @param filename is absolute pathname */ closeClientFile(filename: string) { - const info = ts.lookUp(this.filenameToScriptInfo, filename); + const info = ts.getProperty(this.filenameToScriptInfo, filename); if (info) { this.closeOpenFile(info); info.isOpen = false; @@ -1255,14 +1254,14 @@ namespace ts.server { } getProjectForFile(filename: string) { - const scriptInfo = ts.lookUp(this.filenameToScriptInfo, filename); + const scriptInfo = ts.getProperty(this.filenameToScriptInfo, filename); if (scriptInfo) { return scriptInfo.defaultProject; } } printProjectsForFile(filename: string) { - const scriptInfo = ts.lookUp(this.filenameToScriptInfo, filename); + const scriptInfo = ts.getProperty(this.filenameToScriptInfo, filename); if (scriptInfo) { this.psLogger.startGroup(); this.psLogger.info("Projects for " + filename); diff --git a/src/server/session.ts b/src/server/session.ts index 7e1ca81e2af8c..f57289d7f411f 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1061,28 +1061,28 @@ namespace ts.server { return { response, responseRequired: true }; } - private handlers: Map<(request: protocol.Request) => { response?: any, responseRequired?: boolean }> = { - [CommandNames.Exit]: () => { + private handlers = new ts.StringMap<(request: protocol.Request) => { response?: any, responseRequired?: boolean }>([ + [CommandNames.Exit, () => { this.exit(); return { responseRequired: false }; - }, - [CommandNames.Definition]: (request: protocol.Request) => { + }], + [CommandNames.Definition, (request: protocol.Request) => { const defArgs = request.arguments; return { response: this.getDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true }; - }, - [CommandNames.TypeDefinition]: (request: protocol.Request) => { + }], + [CommandNames.TypeDefinition, (request: protocol.Request) => { const defArgs = request.arguments; return { response: this.getTypeDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true }; - }, - [CommandNames.References]: (request: protocol.Request) => { + }], + [CommandNames.References, (request: protocol.Request) => { const defArgs = request.arguments; return { response: this.getReferences(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true }; - }, - [CommandNames.Rename]: (request: protocol.Request) => { + }], + [CommandNames.Rename, (request: protocol.Request) => { const renameArgs = request.arguments; return { response: this.getRenameLocations(renameArgs.line, renameArgs.offset, renameArgs.file, renameArgs.findInComments, renameArgs.findInStrings), responseRequired: true }; - }, - [CommandNames.Open]: (request: protocol.Request) => { + }], + [CommandNames.Open, (request: protocol.Request) => { const openArgs = request.arguments; let scriptKind: ScriptKind; switch (openArgs.scriptKindName) { @@ -1101,113 +1101,113 @@ namespace ts.server { } this.openClientFile(openArgs.file, openArgs.fileContent, scriptKind); return { responseRequired: false }; - }, - [CommandNames.Quickinfo]: (request: protocol.Request) => { + }], + [CommandNames.Quickinfo, (request: protocol.Request) => { const quickinfoArgs = request.arguments; return { response: this.getQuickInfo(quickinfoArgs.line, quickinfoArgs.offset, quickinfoArgs.file), responseRequired: true }; - }, - [CommandNames.Format]: (request: protocol.Request) => { + }], + [CommandNames.Format, (request: protocol.Request) => { const formatArgs = request.arguments; return { response: this.getFormattingEditsForRange(formatArgs.line, formatArgs.offset, formatArgs.endLine, formatArgs.endOffset, formatArgs.file), responseRequired: true }; - }, - [CommandNames.Formatonkey]: (request: protocol.Request) => { + }], + [CommandNames.Formatonkey, (request: protocol.Request) => { const formatOnKeyArgs = request.arguments; return { response: this.getFormattingEditsAfterKeystroke(formatOnKeyArgs.line, formatOnKeyArgs.offset, formatOnKeyArgs.key, formatOnKeyArgs.file), responseRequired: true }; - }, - [CommandNames.Completions]: (request: protocol.Request) => { + }], + [CommandNames.Completions, (request: protocol.Request) => { const completionsArgs = request.arguments; return { response: this.getCompletions(completionsArgs.line, completionsArgs.offset, completionsArgs.prefix, completionsArgs.file), responseRequired: true }; - }, - [CommandNames.CompletionDetails]: (request: protocol.Request) => { + }], + [CommandNames.CompletionDetails, (request: protocol.Request) => { const completionDetailsArgs = request.arguments; return { response: this.getCompletionEntryDetails(completionDetailsArgs.line, completionDetailsArgs.offset, completionDetailsArgs.entryNames, completionDetailsArgs.file), responseRequired: true }; - }, - [CommandNames.SignatureHelp]: (request: protocol.Request) => { + }], + [CommandNames.SignatureHelp, (request: protocol.Request) => { const signatureHelpArgs = request.arguments; return { response: this.getSignatureHelpItems(signatureHelpArgs.line, signatureHelpArgs.offset, signatureHelpArgs.file), responseRequired: true }; - }, - [CommandNames.SemanticDiagnosticsSync]: (request: protocol.FileRequest) => { + }], + [CommandNames.SemanticDiagnosticsSync, (request: protocol.FileRequest) => { return this.requiredResponse(this.getSemanticDiagnosticsSync(request.arguments)); - }, - [CommandNames.SyntacticDiagnosticsSync]: (request: protocol.FileRequest) => { + }], + [CommandNames.SyntacticDiagnosticsSync, (request: protocol.FileRequest) => { return this.requiredResponse(this.getSyntacticDiagnosticsSync(request.arguments)); - }, - [CommandNames.Geterr]: (request: protocol.Request) => { + }], + [CommandNames.Geterr, (request: protocol.Request) => { const geterrArgs = request.arguments; return { response: this.getDiagnostics(geterrArgs.delay, geterrArgs.files), responseRequired: false }; - }, - [CommandNames.GeterrForProject]: (request: protocol.Request) => { + }], + [CommandNames.GeterrForProject, (request: protocol.Request) => { const { file, delay } = request.arguments; return { response: this.getDiagnosticsForProject(delay, file), responseRequired: false }; - }, - [CommandNames.Change]: (request: protocol.Request) => { + }], + [CommandNames.Change, (request: protocol.Request) => { const changeArgs = request.arguments; this.change(changeArgs.line, changeArgs.offset, changeArgs.endLine, changeArgs.endOffset, changeArgs.insertString, changeArgs.file); return { responseRequired: false }; - }, - [CommandNames.Configure]: (request: protocol.Request) => { + }], + [CommandNames.Configure, (request: protocol.Request) => { const configureArgs = request.arguments; this.projectService.setHostConfiguration(configureArgs); this.output(undefined, CommandNames.Configure, request.seq); return { responseRequired: false }; - }, - [CommandNames.Reload]: (request: protocol.Request) => { + }], + [CommandNames.Reload, (request: protocol.Request) => { const reloadArgs = request.arguments; this.reload(reloadArgs.file, reloadArgs.tmpfile, request.seq); return { response: { reloadFinished: true }, responseRequired: true }; - }, - [CommandNames.Saveto]: (request: protocol.Request) => { + }], + [CommandNames.Saveto, (request: protocol.Request) => { const savetoArgs = request.arguments; this.saveToTmp(savetoArgs.file, savetoArgs.tmpfile); return { responseRequired: false }; - }, - [CommandNames.Close]: (request: protocol.Request) => { + }], + [CommandNames.Close, (request: protocol.Request) => { const closeArgs = request.arguments; this.closeClientFile(closeArgs.file); return { responseRequired: false }; - }, - [CommandNames.Navto]: (request: protocol.Request) => { + }], + [CommandNames.Navto, (request: protocol.Request) => { const navtoArgs = request.arguments; return { response: this.getNavigateToItems(navtoArgs.searchValue, navtoArgs.file, navtoArgs.maxResultCount), responseRequired: true }; - }, - [CommandNames.Brace]: (request: protocol.Request) => { + }], + [CommandNames.Brace, (request: protocol.Request) => { const braceArguments = request.arguments; return { response: this.getBraceMatching(braceArguments.line, braceArguments.offset, braceArguments.file), responseRequired: true }; - }, - [CommandNames.NavBar]: (request: protocol.Request) => { + }], + [CommandNames.NavBar, (request: protocol.Request) => { const navBarArgs = request.arguments; return { response: this.getNavigationBarItems(navBarArgs.file), responseRequired: true }; - }, - [CommandNames.Occurrences]: (request: protocol.Request) => { + }], + [CommandNames.Occurrences, (request: protocol.Request) => { const { line, offset, file: fileName } = request.arguments; return { response: this.getOccurrences(line, offset, fileName), responseRequired: true }; - }, - [CommandNames.DocumentHighlights]: (request: protocol.Request) => { + }], + [CommandNames.DocumentHighlights, (request: protocol.Request) => { const { line, offset, file: fileName, filesToSearch } = request.arguments; return { response: this.getDocumentHighlights(line, offset, fileName, filesToSearch), responseRequired: true }; - }, - [CommandNames.ProjectInfo]: (request: protocol.Request) => { + }], + [CommandNames.ProjectInfo, (request: protocol.Request) => { const { file, needFileNameList } = request.arguments; return { response: this.getProjectInfo(file, needFileNameList), responseRequired: true }; - }, - [CommandNames.ReloadProjects]: (request: protocol.ReloadProjectsRequest) => { + }], + [CommandNames.ReloadProjects, (request: protocol.ReloadProjectsRequest) => { this.reloadProjects(); return { responseRequired: false }; - } - }; + }] + ]); public addProtocolHandler(command: string, handler: (request: protocol.Request) => { response?: any, responseRequired: boolean }) { - if (this.handlers[command]) { + if (this.handlers.has(command)) { throw new Error(`Protocol handler already exists for command "${command}"`); } - this.handlers[command] = handler; + this.handlers.set(command, handler); } public executeCommand(request: protocol.Request): { response?: any, responseRequired?: boolean } { - const handler = this.handlers[request.command]; + const handler = this.handlers.get(request.command); if (handler) { return handler(request); } diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index 3b013a4a924ac..4d57d3430c7b2 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -47,7 +47,7 @@ namespace ts.JsTyping { { cachedTypingPaths: string[], newTypingNames: string[], filesToWatch: string[] } { // A typing name to typing file path mapping - const inferredTypings: Map = {}; + const inferredTypings = new StringMap(); if (!typingOptions || !typingOptions.enableAutoDiscovery) { return { cachedTypingPaths: [], newTypingNames: [], filesToWatch: [] }; @@ -93,26 +93,26 @@ namespace ts.JsTyping { // Add the cached typing locations for inferred typings that are already installed for (const name in packageNameToTypingLocation) { - if (hasProperty(inferredTypings, name) && !inferredTypings[name]) { - inferredTypings[name] = packageNameToTypingLocation[name]; + if (inferredTypings.has(name) && !inferredTypings.get(name)) { + inferredTypings.set(name, packageNameToTypingLocation[name]); } } // Remove typings that the user has added to the exclude list for (const excludeTypingName of exclude) { - delete inferredTypings[excludeTypingName]; + inferredTypings.delete(excludeTypingName); } const newTypingNames: string[] = []; const cachedTypingPaths: string[] = []; - for (const typing in inferredTypings) { - if (inferredTypings[typing] !== undefined) { - cachedTypingPaths.push(inferredTypings[typing]); + inferredTypings.forEach((inferredTypingPath, typingName) => { + if (inferredTypingPath !== undefined) { + cachedTypingPaths.push(inferredTypingPath); } else { - newTypingNames.push(typing); + newTypingNames.push(typingName); } - } + }); return { cachedTypingPaths, newTypingNames, filesToWatch }; /** @@ -124,8 +124,8 @@ namespace ts.JsTyping { } for (const typing of typingNames) { - if (!hasProperty(inferredTypings, typing)) { - inferredTypings[typing] = undefined; + if (!inferredTypings.has(typing)) { + inferredTypings.set(typing, undefined); } } } @@ -214,7 +214,7 @@ namespace ts.JsTyping { } if (packageJson.typings) { const absolutePath = getNormalizedAbsolutePath(packageJson.typings, getDirectoryPath(normalizedFileName)); - inferredTypings[packageJson.name] = absolutePath; + inferredTypings.set(packageJson.name, absolutePath); } else { typingNames.push(packageJson.name); diff --git a/src/services/navigateTo.ts b/src/services/navigateTo.ts index e9afb6925b53f..4240362ce6136 100644 --- a/src/services/navigateTo.ts +++ b/src/services/navigateTo.ts @@ -14,39 +14,36 @@ namespace ts.NavigateTo { cancellationToken.throwIfCancellationRequested(); const nameToDeclarations = sourceFile.getNamedDeclarations(); - for (const name in nameToDeclarations) { - const declarations = getProperty(nameToDeclarations, name); - if (declarations) { - // First do a quick check to see if the name of the declaration matches the - // last portion of the (possibly) dotted name they're searching for. - let matches = patternMatcher.getMatchesForLastSegmentOfPattern(name); - - if (!matches) { - continue; - } - - for (const declaration of declarations) { - // It was a match! If the pattern has dots in it, then also see if the - // declaration container matches as well. - if (patternMatcher.patternContainsDots) { - const containers = getContainers(declaration); - if (!containers) { - return undefined; - } + nameToDeclarations.forEach((declarations, name) => { + // First do a quick check to see if the name of the declaration matches the + // last portion of the (possibly) dotted name they're searching for. + let matches = patternMatcher.getMatchesForLastSegmentOfPattern(name); - matches = patternMatcher.getMatches(containers, name); + if (!matches) { + return; + } - if (!matches) { - continue; - } + for (const declaration of declarations) { + // It was a match! If the pattern has dots in it, then also see if the + // declaration container matches as well. + if (patternMatcher.patternContainsDots) { + const containers = getContainers(declaration); + if (!containers) { + return undefined; } - const fileName = sourceFile.fileName; - const matchKind = bestMatchKind(matches); - rawItems.push({ name, fileName, matchKind, isCaseSensitive: allMatchesAreCaseSensitive(matches), declaration }); + matches = patternMatcher.getMatches(containers, name); + + if (!matches) { + continue; + } } + + const fileName = sourceFile.fileName; + const matchKind = bestMatchKind(matches); + rawItems.push({ name, fileName, matchKind, isCaseSensitive: allMatchesAreCaseSensitive(matches), declaration }); } - } + }); }); // Remove imports when the imported declaration is already in the list and has the same name. diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index f105a9fa6e23b..56a219f96cd47 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -234,7 +234,7 @@ namespace ts.NavigationBar { /** Merge declarations of the same kind. */ function mergeChildren(children: NavigationBarNode[]): void { - const nameToItems: Map = {}; + const nameToItems = new ts.StringMap(); filterMutate(children, child => { const decl = child.node; const name = decl.name && nodeText(decl.name); @@ -243,9 +243,9 @@ namespace ts.NavigationBar { return true; } - const itemsWithSameName = getProperty(nameToItems, name); + const itemsWithSameName = nameToItems.get(name); if (!itemsWithSameName) { - nameToItems[name] = child; + nameToItems.set(name, child); return true; } @@ -263,7 +263,7 @@ namespace ts.NavigationBar { if (tryMerge(itemWithSameName, child)) { return false; } - nameToItems[name] = [itemWithSameName, child]; + nameToItems.set(name, [itemWithSameName, child]); return true; } diff --git a/src/services/patternMatcher.ts b/src/services/patternMatcher.ts index 3d20337eca775..f8c5c33e48f1e 100644 --- a/src/services/patternMatcher.ts +++ b/src/services/patternMatcher.ts @@ -113,7 +113,7 @@ namespace ts { // we see the name of a module that is used everywhere, or the name of an overload). As // such, we cache the information we compute about the candidate for the life of this // pattern matcher so we don't have to compute it multiple times. - const stringToWordSpans: Map = {}; + const stringToWordSpans = new ts.StringMap(); pattern = pattern.trim(); @@ -188,11 +188,7 @@ namespace ts { } function getWordSpans(word: string): TextSpan[] { - if (!hasProperty(stringToWordSpans, word)) { - stringToWordSpans[word] = breakIntoWordSpans(word); - } - - return stringToWordSpans[word]; + return getOrUpdate(stringToWordSpans, word, () => breakIntoWordSpans(word)); } function matchTextChunk(candidate: string, chunk: TextChunk, punctuationStripped: boolean): PatternMatch { diff --git a/src/services/services.ts b/src/services/services.ts index aea4d5f872e03..903d0a6bce586 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -65,9 +65,9 @@ namespace ts { export interface SourceFile { /* @internal */ version: string; /* @internal */ scriptSnapshot: IScriptSnapshot; - /* @internal */ nameTable: Map; + /* @internal */ nameTable: ts.StringMap; - /* @internal */ getNamedDeclarations(): Map; + /* @internal */ getNamedDeclarations(): ts.StringMap; getLineAndCharacterOfPosition(pos: number): LineAndCharacter; getLineStarts(): number[]; @@ -938,13 +938,13 @@ namespace ts { public scriptKind: ScriptKind; public languageVersion: ScriptTarget; public languageVariant: LanguageVariant; - public identifiers: Map; - public nameTable: Map; - public resolvedModules: Map; - public resolvedTypeReferenceDirectiveNames: Map; + public identifiers: ts.StringMap; + public nameTable: ts.StringMap; + public resolvedModules: ts.StringMap; + public resolvedTypeReferenceDirectiveNames: ts.StringMap; public imports: LiteralExpression[]; public moduleAugmentations: LiteralExpression[]; - private namedDeclarations: Map; + private namedDeclarations: ts.StringMap; constructor(kind: SyntaxKind, pos: number, end: number) { super(kind, pos, end); @@ -966,7 +966,7 @@ namespace ts { return ts.getPositionOfLineAndCharacter(this, line, character); } - public getNamedDeclarations(): Map { + public getNamedDeclarations(): ts.StringMap { if (!this.namedDeclarations) { this.namedDeclarations = this.computeNamedDeclarations(); } @@ -974,8 +974,8 @@ namespace ts { return this.namedDeclarations; } - private computeNamedDeclarations(): Map { - const result: Map = {}; + private computeNamedDeclarations(): ts.StringMap { + const result = new ts.StringMap(); forEachChild(this, visit); @@ -990,7 +990,7 @@ namespace ts { } function getDeclarations(name: string) { - return getProperty(result, name) || (result[name] = []); + return getOrUpdate(result, name, () => []); } function getDeclarationName(declaration: Declaration) { @@ -2042,7 +2042,7 @@ namespace ts { function fixupCompilerOptions(options: CompilerOptions, diagnostics: Diagnostic[]): CompilerOptions { // Lazily create this value to fix module loading errors. commandLineOptionsStringToEnum = commandLineOptionsStringToEnum || filter(optionDeclarations, o => - typeof o.type === "object" && !forEachValue(>o.type, v => typeof v !== "number")); + typeof o.type === "object" && allInStringMap(o.type, v => typeof v === "number")); options = clone(options); @@ -2058,7 +2058,7 @@ namespace ts { options[opt.name] = parseCustomTypeOption(opt, value, diagnostics); } else { - if (!forEachValue(opt.type, v => v === value)) { + if (!someInStringMap(opt.type, v => v === value)) { // Supplied value isn't a valid enum value. diagnostics.push(createCompilerDiagnosticForInvalidCustomType(opt)); } @@ -2117,7 +2117,7 @@ namespace ts { sourceFile.moduleName = transpileOptions.moduleName; } - sourceFile.renamedDependencies = transpileOptions.renamedDependencies; + sourceFile.renamedDependencies = stringMapOfMap(transpileOptions.renamedDependencies); const newLine = getNewLineCharacter(options); @@ -2243,7 +2243,7 @@ namespace ts { export function createDocumentRegistry(useCaseSensitiveFileNames?: boolean, currentDirectory = ""): DocumentRegistry { // Maps from compiler setting target (ES3, ES5, etc.) to all the cached documents we have // for those settings. - const buckets: Map> = {}; + const buckets = new ts.StringMap>(); const getCanonicalFileName = createGetCanonicalFileName(!!useCaseSensitiveFileNames); function getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey { @@ -2251,16 +2251,16 @@ namespace ts { } function getBucketForCompilationSettings(key: DocumentRegistryBucketKey, createIfMissing: boolean): FileMap { - let bucket = lookUp(buckets, key); - if (!bucket && createIfMissing) { - buckets[key] = bucket = createFileMap(); - } - return bucket; + return createIfMissing + ? getOrUpdate>(buckets, key, createFileMap) + : buckets.get(key); } function reportStats() { - const bucketInfoArray = Object.keys(buckets).filter(name => name && name.charAt(0) === "_").map(name => { - const entries = lookUp(buckets, name); + const bucketInfoArray = mapAndFilterStringMap(buckets, (entries, name) => { + if (!(name && name.charAt(0) === "_")) { + return; + } const sourceFiles: { name: string; refCount: number; references: string[]; }[] = []; entries.forEachValue((key, entry) => { sourceFiles.push({ @@ -4102,7 +4102,7 @@ namespace ts { * do not occur at the current position and have not otherwise been typed. */ function filterNamedImportOrExportCompletionItems(exportsOfModule: Symbol[], namedImportsOrExports: ImportOrExportSpecifier[]): Symbol[] { - const existingImportsOrExports: Map = {}; + const existingImportsOrExports = new StringSet(); for (const element of namedImportsOrExports) { // If this is the current item we are editing right now, do not filter it out @@ -4111,14 +4111,14 @@ namespace ts { } const name = element.propertyName || element.name; - existingImportsOrExports[name.text] = true; + existingImportsOrExports.add(name.text); } - if (isEmpty(existingImportsOrExports)) { + if (isSetEmpty(existingImportsOrExports)) { return filter(exportsOfModule, e => e.name !== "default"); } - return filter(exportsOfModule, e => e.name !== "default" && !lookUp(existingImportsOrExports, e.name)); + return filter(exportsOfModule, e => e.name !== "default" && !existingImportsOrExports.has(e.name)); } /** @@ -4132,7 +4132,7 @@ namespace ts { return contextualMemberSymbols; } - const existingMemberNames: Map = {}; + const existingMemberNames = new StringSet(); for (const m of existingMembers) { // Ignore omitted expressions for missing members if (m.kind !== SyntaxKind.PropertyAssignment && @@ -4162,10 +4162,10 @@ namespace ts { existingName = (m.name).text; } - existingMemberNames[existingName] = true; + existingMemberNames.add(existingName); } - return filter(contextualMemberSymbols, m => !lookUp(existingMemberNames, m.name)); + return filter(contextualMemberSymbols, m => !existingMemberNames.has(m.name)); } /** @@ -4175,7 +4175,7 @@ namespace ts { * do not occur at the current position and have not otherwise been typed. */ function filterJsxAttributes(symbols: Symbol[], attributes: NodeArray): Symbol[] { - const seenNames: Map = {}; + const seenNames = new StringSet(); for (const attr of attributes) { // If this is the current item we are editing right now, do not filter it out if (attr.getStart() <= position && position <= attr.getEnd()) { @@ -4183,11 +4183,11 @@ namespace ts { } if (attr.kind === SyntaxKind.JsxAttribute) { - seenNames[(attr).name.text] = true; + seenNames.add((attr).name.text); } } - return filter(symbols, a => !lookUp(seenNames, a.name)); + return filter(symbols, a => !seenNames.has(a.name)); } } @@ -4249,19 +4249,19 @@ namespace ts { return { isMemberCompletion, isNewIdentifierLocation, entries }; - function getJavaScriptCompletionEntries(sourceFile: SourceFile, position: number, uniqueNames: Map): CompletionEntry[] { + function getJavaScriptCompletionEntries(sourceFile: SourceFile, position: number, uniqueNames: StringSet): CompletionEntry[] { const entries: CompletionEntry[] = []; const target = program.getCompilerOptions().target; const nameTable = getNameTable(sourceFile); - for (const name in nameTable) { + nameTable.forEach((pos, name) => { // Skip identifiers produced only from the current location - if (nameTable[name] === position) { - continue; + if (pos === position) { + return; } - if (!uniqueNames[name]) { - uniqueNames[name] = name; + if (!uniqueNames.has(name)) { + uniqueNames.add(name); const displayName = getCompletionEntryDisplayName(unescapeIdentifier(name), target, /*performCharacterChecks*/ true); if (displayName) { const entry = { @@ -4273,7 +4273,7 @@ namespace ts { entries.push(entry); } } - } + }); return entries; } @@ -4315,17 +4315,17 @@ namespace ts { } - function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: CompletionEntry[], location: Node, performCharacterChecks: boolean): Map { + function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: CompletionEntry[], location: Node, performCharacterChecks: boolean): StringSet { const start = timestamp(); - const uniqueNames: Map = {}; + const uniqueNames = new StringSet(); if (symbols) { for (const symbol of symbols) { const entry = createCompletionEntry(symbol, location, performCharacterChecks); if (entry) { const id = escapeIdentifier(entry.name); - if (!lookUp(uniqueNames, id)) { + if (!uniqueNames.has(id)) { entries.push(entry); - uniqueNames[id] = id; + uniqueNames.add(id); } } } @@ -5151,7 +5151,7 @@ namespace ts { // Type reference directives const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position); if (typeReferenceDirective) { - const referenceFile = lookUp(program.getResolvedTypeReferenceDirectives(), typeReferenceDirective.fileName); + const referenceFile = program.getResolvedTypeReferenceDirectives().get(typeReferenceDirective.fileName); if (referenceFile && referenceFile.resolvedFileName) { return [getDefinitionInfoForFileReference(typeReferenceDirective.fileName, referenceFile.resolvedFileName)]; } @@ -5318,16 +5318,16 @@ namespace ts { return undefined; } - const fileNameToDocumentHighlights: Map = {}; + const fileNameToDocumentHighlights = new StringMap(); const result: DocumentHighlights[] = []; for (const referencedSymbol of referencedSymbols) { for (const referenceEntry of referencedSymbol.references) { const fileName = referenceEntry.fileName; - let documentHighlights = getProperty(fileNameToDocumentHighlights, fileName); + let documentHighlights = fileNameToDocumentHighlights.get(fileName); if (!documentHighlights) { documentHighlights = { fileName, highlightSpans: [] }; - fileNameToDocumentHighlights[fileName] = documentHighlights; + fileNameToDocumentHighlights.set(fileName, documentHighlights); result.push(documentHighlights); } @@ -6068,7 +6068,7 @@ namespace ts { const nameTable = getNameTable(sourceFile); - if (lookUp(nameTable, internedName) !== undefined) { + if (nameTable.has(internedName)) { result = result || []; getReferencesInNode(sourceFile, symbol, declaredName, node, searchMeaning, findInStrings, findInComments, result, symbolToIndex); } @@ -6713,7 +6713,7 @@ namespace ts { // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ {}); + getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ new StringMap()); } }); @@ -6745,7 +6745,7 @@ namespace ts { // the function will add any found symbol of the property-name, then its sub-routine will call // getPropertySymbolsFromBaseTypes again to walk up any base types to prevent revisiting already // visited symbol, interface "C", the sub-routine will pass the current symbol as previousIterationSymbol. - if (hasProperty(previousIterationSymbolsCache, symbol.name)) { + if (previousIterationSymbolsCache.has(symbol.name)) { return; } @@ -6772,7 +6772,7 @@ namespace ts { } // Visit the typeReference as well to see if it directly or indirectly use that property - previousIterationSymbolsCache[symbol.name] = symbol; + previousIterationSymbolsCache.set(symbol.name, symbol); getPropertySymbolsFromBaseTypes(type.symbol, propertyName, result, previousIterationSymbolsCache); } } @@ -6834,7 +6834,7 @@ namespace ts { // see if any is in the list if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { const result: Symbol[] = []; - getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ {}); + getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ new StringMap()); return forEach(result, s => searchSymbols.indexOf(s) >= 0 ? s : undefined); } @@ -7314,7 +7314,7 @@ namespace ts { // Only bother calling into the typechecker if this is an identifier that // could possibly resolve to a type name. This makes classification run // in a third of the time it would normally take. - if (classifiableNames[identifier.text]) { + if (classifiableNames.has(identifier.text)) { const symbol = typeChecker.getSymbolAtLocation(node); if (symbol) { const type = classifySymbol(symbol, getMeaningFromLocation(node)); @@ -8309,7 +8309,7 @@ namespace ts { } /* @internal */ - export function getNameTable(sourceFile: SourceFile): Map { + export function getNameTable(sourceFile: SourceFile): StringMap { if (!sourceFile.nameTable) { initializeNameTable(sourceFile); } @@ -8318,7 +8318,7 @@ namespace ts { } function initializeNameTable(sourceFile: SourceFile): void { - const nameTable: Map = {}; + const nameTable = new StringMap(); walk(sourceFile); sourceFile.nameTable = nameTable; @@ -8326,7 +8326,7 @@ namespace ts { function walk(node: Node) { switch (node.kind) { case SyntaxKind.Identifier: - nameTable[(node).text] = nameTable[(node).text] === undefined ? node.pos : -1; + addName((node).text, node.pos); break; case SyntaxKind.StringLiteral: case SyntaxKind.NumericLiteral: @@ -8339,7 +8339,7 @@ namespace ts { isArgumentOfElementAccessExpression(node) || isLiteralComputedPropertyDeclarationName(node)) { - nameTable[(node).text] = nameTable[(node).text] === undefined ? node.pos : -1; + addName((node).text, node.pos); } break; default: @@ -8351,6 +8351,10 @@ namespace ts { } } } + + function addName(name: string, pos: number) { + nameTable.set(name, nameTable.has(name) ? -1 : pos); + } } function isArgumentOfElementAccessExpression(node: Node) { diff --git a/src/services/shims.ts b/src/services/shims.ts index 45c4b284ae744..a8ecf9b6d273b 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -313,7 +313,7 @@ namespace ts { this.resolveModuleNames = (moduleNames: string[], containingFile: string) => { const resolutionsInFile = >JSON.parse(this.shimHost.getModuleResolutionsForFile(containingFile)); return map(moduleNames, name => { - const result = lookUp(resolutionsInFile, name); + const result = getProperty(resolutionsInFile, name); return result ? { resolvedFileName: result } : undefined; }); }; @@ -324,7 +324,7 @@ namespace ts { if ("getTypeReferenceDirectiveResolutionsForFile" in this.shimHost) { this.resolveTypeReferenceDirectives = (typeDirectiveNames: string[], containingFile: string) => { const typeDirectivesForFile = >JSON.parse(this.shimHost.getTypeReferenceDirectiveResolutionsForFile(containingFile)); - return map(typeDirectiveNames, name => lookUp(typeDirectivesForFile, name)); + return map(typeDirectiveNames, name => getProperty(typeDirectivesForFile, name)); }; } } diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index 50378aa64b1a1..4f6d0c117a8cf 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -237,18 +237,16 @@ namespace ts.SignatureHelp { const typeChecker = program.getTypeChecker(); for (const sourceFile of program.getSourceFiles()) { const nameToDeclarations = sourceFile.getNamedDeclarations(); - const declarations = getProperty(nameToDeclarations, name.text); - - if (declarations) { - for (const declaration of declarations) { - const symbol = declaration.symbol; - if (symbol) { - const type = typeChecker.getTypeOfSymbolAtLocation(symbol, declaration); - if (type) { - const callSignatures = type.getCallSignatures(); - if (callSignatures && callSignatures.length) { - return createSignatureHelpItems(callSignatures, callSignatures[0], argumentInfo, typeChecker); - } + const declarations = nameToDeclarations.get(name.text); + + for (const declaration of declarations) { + const symbol = declaration.symbol; + if (symbol) { + const type = typeChecker.getTypeOfSymbolAtLocation(symbol, declaration); + if (type) { + const callSignatures = type.getCallSignatures(); + if (callSignatures && callSignatures.length) { + return createSignatureHelpItems(callSignatures, callSignatures[0], argumentInfo, typeChecker); } } } diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 9ce3197a46f73..afca901c7cc6c 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -43,6 +43,10 @@ // TODO: figure out a better solution to the API exposure problem. declare module ts { + interface StringMap { + forEach(fn: (value: V, key: string) => void); + } + interface SymbolDisplayPart { text: string; kind: string; @@ -101,7 +105,7 @@ declare namespace FourSlashInterface { markers(): Marker[]; marker(name?: string): Marker; ranges(): Range[]; - rangesByText(): { [text: string]: Range[] }; + rangesByText(): ts.StringMap; markerByName(s: string): Marker; } class goTo { diff --git a/tests/cases/fourslash/localGetReferences.ts b/tests/cases/fourslash/localGetReferences.ts index ada4947acde52..c0780fc733c96 100644 --- a/tests/cases/fourslash/localGetReferences.ts +++ b/tests/cases/fourslash/localGetReferences.ts @@ -201,9 +201,7 @@ verify.referencesAre([]); goTo.marker("4"); verify.referencesAre([]); -const rangesByText = test.rangesByText(); -for (const text in rangesByText) { - const ranges = rangesByText[text]; +test.rangesByText().forEach((ranges, text) => { if (text === "globalVar") { function isShadow(r) { return r.marker && r.marker.data && r.marker.data.shadow; @@ -213,4 +211,4 @@ for (const text in rangesByText) { } else { verify.rangesReferenceEachOther(ranges); } -} +});