From 10fd923d5ab731d982433c69e5d825975fbda80d Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 11 Aug 2016 22:30:29 -0700 Subject: [PATCH 1/2] Faster maps --- scripts/processDiagnosticMessages.ts | 12 +- src/compiler/binder.ts | 24 +- src/compiler/checker.ts | 275 ++++---- src/compiler/commandLineParser.ts | 61 +- src/compiler/core.ts | 619 +++++++++++++++--- src/compiler/declarationEmitter.ts | 14 +- src/compiler/emitter.ts | 60 +- src/compiler/parser.ts | 4 +- src/compiler/performance.ts | 26 +- src/compiler/program.ts | 39 +- src/compiler/scanner.ts | 10 +- src/compiler/sys.ts | 14 +- src/compiler/tsc.ts | 75 +-- src/compiler/types.ts | 18 +- src/compiler/utilities.ts | 45 +- src/harness/compilerRunner.ts | 8 +- src/harness/fourslash.ts | 32 +- src/harness/harness.ts | 8 +- src/harness/harnessLanguageService.ts | 10 +- src/harness/projectsRunner.ts | 8 +- src/harness/rwcRunner.ts | 2 +- .../unittests/cachingInServerLSHost.ts | 16 +- src/harness/unittests/matchFiles.ts | 160 ++--- src/harness/unittests/moduleResolution.ts | 58 +- .../unittests/reuseProgramStructure.ts | 30 +- src/harness/unittests/session.ts | 14 +- .../unittests/tsserverProjectSystem.ts | 32 +- src/server/client.ts | 6 +- src/server/editorServices.ts | 44 +- src/server/session.ts | 8 +- src/services/formatting/rules.ts | 2 +- src/services/formatting/rulesProvider.ts | 2 +- src/services/jsTyping.ts | 35 +- src/services/navigateTo.ts | 4 +- src/services/navigationBar.ts | 4 +- src/services/patternMatcher.ts | 4 +- src/services/services.ts | 60 +- src/services/shims.ts | 8 +- src/services/signatureHelp.ts | 2 +- tests/cases/compiler/APISample_watcher.ts | 4 +- 40 files changed, 1100 insertions(+), 757 deletions(-) diff --git a/scripts/processDiagnosticMessages.ts b/scripts/processDiagnosticMessages.ts index 26632ba6bab3c..dc99a58b0257d 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,8 +68,8 @@ function checkForUniqueCodes(messages: string[], diagnosticTable: InputDiagnosti } } -function buildUniqueNameMap(names: string[]): ts.Map { - var nameMap: ts.Map = {}; +function buildUniqueNameMap(names: string[]): ts.MapLike { + var nameMap: ts.MapLike = {}; var uniqueNames = NameGenerator.ensureUniqueness(names, /* isCaseSensitive */ false, /* isFixed */ undefined); @@ -80,7 +80,7 @@ function buildUniqueNameMap(names: string[]): ts.Map { return nameMap; } -function buildInfoFileOutput(messageTable: InputDiagnosticMessageTable, nameMap: ts.Map): string { +function buildInfoFileOutput(messageTable: InputDiagnosticMessageTable, nameMap: ts.MapLike): string { var result = '// \r\n' + '/// \r\n' + @@ -107,7 +107,7 @@ function buildInfoFileOutput(messageTable: InputDiagnosticMessageTable, nameMap: return result; } -function buildDiagnosticMessageOutput(messageTable: InputDiagnosticMessageTable, nameMap: ts.Map): string { +function buildDiagnosticMessageOutput(messageTable: InputDiagnosticMessageTable, nameMap: ts.MapLike): string { var result = '{'; var names = Utilities.getObjectKeys(messageTable); diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 502cb39e8fe55..8e05ed1582406 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -135,7 +135,7 @@ namespace ts { options = opts; languageVersion = getEmitScriptTarget(options); inStrictMode = !!file.externalModuleIndicator; - classifiableNames = {}; + classifiableNames = Map.create(); 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 = Map.create(); } if (symbolFlags & SymbolFlags.HasMembers && !symbol.members) { - symbol.members = {}; + symbol.members = Map.create(); } if (symbolFlags & SymbolFlags.Value) { @@ -318,7 +318,7 @@ namespace ts { // Otherwise, we'll be merging into a compatible existing symbol (for example when // you have multiple 'vars' with the same name in the same container). In this case // just add this node into the declarations list of the symbol. - symbol = hasProperty(symbolTable, name) + symbol = Map.has(symbolTable, name) ? symbolTable[name] : (symbolTable[name] = createSymbol(SymbolFlags.None, name)); @@ -434,7 +434,7 @@ namespace ts { if (containerFlags & ContainerFlags.IsContainer) { container = blockScopeContainer = node; if (containerFlags & ContainerFlags.HasLocals) { - container.locals = {}; + container.locals = Map.create(); } addToContainerChain(container); } @@ -1399,7 +1399,7 @@ namespace ts { const typeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, "__type"); addDeclarationToSymbol(typeLiteralSymbol, node, SymbolFlags.TypeLiteral); - typeLiteralSymbol.members = { [symbol.name]: symbol }; + typeLiteralSymbol.members = Map.create({ [symbol.name]: symbol }); } function bindObjectLiteralExpression(node: ObjectLiteralExpression) { @@ -1409,7 +1409,7 @@ namespace ts { } if (inStrictMode) { - const seen: Map = {}; + const seen = Map.create(); for (const prop of node.properties) { if (prop.name.kind !== SyntaxKind.Identifier) { @@ -1465,7 +1465,7 @@ namespace ts { // fall through. default: if (!blockScopeContainer.locals) { - blockScopeContainer.locals = {}; + blockScopeContainer.locals = Map.create(); addToContainerChain(blockScopeContainer); } declareSymbol(blockScopeContainer.locals, undefined, node, symbolFlags, symbolExcludes); @@ -1924,7 +1924,7 @@ namespace ts { } } - file.symbol.globalExports = file.symbol.globalExports || {}; + file.symbol.globalExports = file.symbol.globalExports || Map.create(); declareSymbol(file.symbol.globalExports, file.symbol, node, SymbolFlags.Alias, SymbolFlags.AliasExcludes); } @@ -1977,7 +1977,7 @@ namespace ts { else { return; } - assignee.symbol.members = assignee.symbol.members || {}; + assignee.symbol.members = assignee.symbol.members || Map.create(); // 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); } @@ -2003,7 +2003,7 @@ namespace ts { // Set up the members collection if it doesn't exist already if (!funcSymbol.members) { - funcSymbol.members = {}; + funcSymbol.members = Map.create(); } // Declare the method/property @@ -2052,7 +2052,7 @@ namespace ts { // module might have an exported variable called 'prototype'. We can't allow that as // that would clash with the built-in 'prototype' for the class. const prototypeSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Prototype, "prototype"); - if (hasProperty(symbol.exports, prototypeSymbol.name)) { + if (Map.has(symbol.exports, prototypeSymbol.name)) { if (node.name) { node.name.parent = node; } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 43ec22df40307..b919c1853e100 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 = Map.create(); 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 = Map.create(); + const unionTypes = Map.create(); + const intersectionTypes = Map.create(); + const stringLiteralTypes = Map.create(); + const numericLiteralTypes = Map.create(); 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 = Map.create(); 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 = Map.create(); /** * List of every ambient module with a "*" wildcard. * Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches. @@ -283,7 +283,7 @@ namespace ts { NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy, } - const typeofEQFacts: Map = { + const typeofEQFacts = Map.create({ "string": TypeFacts.TypeofEQString, "number": TypeFacts.TypeofEQNumber, "boolean": TypeFacts.TypeofEQBoolean, @@ -291,9 +291,9 @@ namespace ts { "undefined": TypeFacts.EQUndefined, "object": TypeFacts.TypeofEQObject, "function": TypeFacts.TypeofEQFunction - }; + }); - const typeofNEFacts: Map = { + const typeofNEFacts = Map.create({ "string": TypeFacts.TypeofNEString, "number": TypeFacts.TypeofNENumber, "boolean": TypeFacts.TypeofNEBoolean, @@ -301,19 +301,19 @@ namespace ts { "undefined": TypeFacts.NEUndefined, "object": TypeFacts.TypeofNEObject, "function": TypeFacts.TypeofNEFunction - }; + }); - const typeofTypesByName: Map = { + const typeofTypesByName = Map.create({ "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 = Map.create(); const JsxNames = { JSX: "JSX", IntrinsicElements: "IntrinsicElements", @@ -324,10 +324,10 @@ namespace ts { IntrinsicClassAttributes: "IntrinsicClassAttributes" }; - const subtypeRelation: Map = {}; - const assignableRelation: Map = {}; - const comparableRelation: Map = {}; - const identityRelation: Map = {}; + const subtypeRelation = Map.create(); + const assignableRelation = Map.create(); + const comparableRelation = Map.create(); + const identityRelation = Map.create(); // This is for caching the result of getSymbolDisplayBuilder. Do not access directly. let _displayBuilder: SymbolDisplayBuilder; @@ -341,9 +341,9 @@ namespace ts { ResolvedReturnType } - const builtinGlobals: SymbolTable = { + const builtinGlobals: SymbolTable = Map.create({ [undefinedSymbol.name]: undefinedSymbol - }; + }); initializeTypeChecker(); @@ -426,11 +426,11 @@ namespace ts { target.declarations.push(node); }); if (source.members) { - if (!target.members) target.members = {}; + if (!target.members) target.members = Map.create(); mergeSymbolTable(target.members, source.members); } if (source.exports) { - if (!target.exports) target.exports = {}; + if (!target.exports) target.exports = Map.create(); mergeSymbolTable(target.exports, source.exports); } recordMergedSymbol(target, source); @@ -448,28 +448,24 @@ namespace ts { } function cloneSymbolTable(symbolTable: SymbolTable): SymbolTable { - const result: SymbolTable = {}; - for (const id in symbolTable) { - if (hasProperty(symbolTable, id)) { - result[id] = symbolTable[id]; - } + const result: SymbolTable = Map.create(); + for (const id in symbolTable) if (Map.guard(symbolTable, id)) { + result[id] = symbolTable[id]; } return result; } function mergeSymbolTable(target: SymbolTable, source: SymbolTable) { - for (const id in source) { - if (hasProperty(source, id)) { - if (!hasProperty(target, id)) { - target[id] = source[id]; - } - else { - let symbol = target[id]; - if (!(symbol.flags & SymbolFlags.Merged)) { - target[id] = symbol = cloneSymbol(symbol); - } - mergeSymbol(symbol, source[id]); + for (const id in source) if (Map.guard(source, id)) { + if (!Map.has(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]); } } } @@ -512,15 +508,13 @@ 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]; - } + for (const id in source) if (Map.guard(source, id)) { + if (Map.has(target, id)) { + // Error on redeclarations + forEach(target[id].declarations, addDeclarationDiagnostic(id, message)); + } + else { + target[id] = source[id]; } } @@ -545,7 +539,7 @@ namespace ts { } function getSymbol(symbols: SymbolTable, name: string, meaning: SymbolFlags): Symbol { - if (meaning && hasProperty(symbols, name)) { + if (meaning && Map.has(symbols, name)) { const symbol = symbols[name]; Debug.assert((symbol.flags & SymbolFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); if (symbol.flags & meaning) { @@ -744,7 +738,7 @@ namespace ts { // 2. We check === SymbolFlags.Alias in order to check that the symbol is *purely* // an alias. If we used &, we'd be throwing out symbols that have non alias aspects, // which is not the desired behavior. - if (hasProperty(moduleExports, name) && + if (Map.has(moduleExports, name) && moduleExports[name].flags === SymbolFlags.Alias && getDeclarationOfKind(moduleExports[name], SyntaxKind.ExportSpecifier)) { break; @@ -1105,7 +1099,7 @@ namespace ts { function getExportOfModule(symbol: Symbol, name: string): Symbol { if (symbol.flags & SymbolFlags.Module) { const exports = getExportsOfSymbol(symbol); - if (hasProperty(exports, name)) { + if (Map.has(exports, name)) { return resolveSymbol(exports[name]); } } @@ -1423,8 +1417,8 @@ namespace ts { * 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)) { + for (const id in source) if (Map.guard(source, id)) { + if (id !== "default" && !Map.has(target, id)) { target[id] = source[id]; if (lookupTable && exportNode) { lookupTable[id] = { @@ -1432,7 +1426,7 @@ namespace ts { } as ExportCollisionTracker; } } - else if (lookupTable && exportNode && id !== "default" && hasProperty(target, id) && resolveSymbol(target[id]) !== resolveSymbol(source[id])) { + else if (lookupTable && exportNode && id !== "default" && Map.has(target, id) && resolveSymbol(target[id]) !== resolveSymbol(source[id])) { if (!lookupTable[id].exportsWithDuplicate) { lookupTable[id].exportsWithDuplicate = [exportNode]; } @@ -1458,8 +1452,8 @@ namespace ts { // All export * declarations are collected in an __export symbol by the binder const exportStars = symbol.exports["__export"]; if (exportStars) { - const nestedSymbols: SymbolTable = {}; - const lookupTable: Map = {}; + const nestedSymbols: SymbolTable = Map.create(); + const lookupTable = Map.create(); for (const node of exportStars.declarations) { const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier); const exportedSymbols = visit(resolvedModule); @@ -1470,10 +1464,10 @@ namespace ts { node as ExportDeclaration ); } - for (const id in lookupTable) { + for (const id in lookupTable) if (Map.guard(lookupTable, id)) { const { exportsWithDuplicate } = lookupTable[id]; // It's not an error if the file with multiple `export *`s with duplicate names exports a member with that name itself - if (id === "export=" || !(exportsWithDuplicate && exportsWithDuplicate.length) || hasProperty(symbols, id)) { + if (id === "export=" || !(exportsWithDuplicate && exportsWithDuplicate.length) || Map.has(symbols, id)) { continue; } for (const node of exportsWithDuplicate) { @@ -1578,14 +1572,12 @@ 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); - } + for (const id in members) if (Map.guard(members, id)) { + if (!isReservedMemberName(id)) { + if (!result) result = []; + const symbol = members[id]; + if (symbolIsValue(symbol)) { + result.push(symbol); } } } @@ -1661,12 +1653,12 @@ namespace ts { } // If symbol is directly available by its name in the symbol table - if (isAccessible(lookUp(symbols, symbol.name))) { + if (isAccessible(Map.get(symbols, symbol.name))) { return [symbol]; } // Check if symbol is any of the alias - return forEachValue(symbols, symbolFromSymbolTable => { + return Map.forEach(symbols, symbolFromSymbolTable => { if (symbolFromSymbolTable.flags & SymbolFlags.Alias && symbolFromSymbolTable.name !== "export=" && !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)) { @@ -1701,7 +1693,7 @@ namespace ts { let qualify = false; forEachSymbolTableInScope(enclosingDeclaration, symbolTable => { // If symbol of this name is not available in the symbol table we are ok - if (!hasProperty(symbolTable, symbol.name)) { + if (!Map.has(symbolTable, symbol.name)) { // Continue to the next symbol table return false; } @@ -3124,7 +3116,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 = Map.create(); let hasComputedProperties = false; forEach(pattern.elements, e => { const name = e.propertyName || e.name; @@ -3712,7 +3704,7 @@ namespace ts { type.typeParameters = concatenate(outerTypeParameters, localTypeParameters); type.outerTypeParameters = outerTypeParameters; type.localTypeParameters = localTypeParameters; - (type).instantiations = {}; + (type).instantiations = Map.create(); (type).instantiations[getTypeListId(type.typeParameters)] = type; (type).target = type; (type).typeArguments = type.typeParameters; @@ -3754,7 +3746,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 = Map.create(); links.instantiations[getTypeListId(links.typeParameters)] = type; } } @@ -3775,7 +3767,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 && Map.has(symbol.exports, (expr).text); } function enumHasLiteralMembers(symbol: Symbol) { @@ -3798,7 +3790,7 @@ namespace ts { enumType.symbol = symbol; if (enumHasLiteralMembers(symbol)) { const memberTypeList: Type[] = []; - const memberTypes: Map = {}; + const memberTypes = Map.create(); for (const declaration of enumType.symbol.declarations) { if (declaration.kind === SyntaxKind.EnumDeclaration) { computeEnumMemberValues(declaration); @@ -3961,7 +3953,7 @@ namespace ts { } function createSymbolTable(symbols: Symbol[]): SymbolTable { - const result: SymbolTable = {}; + const result: SymbolTable = Map.create(); for (const symbol of symbols) { result[symbol.name] = symbol; } @@ -3971,7 +3963,7 @@ namespace ts { // The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true, // we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation. function createInstantiatedSymbolTable(symbols: Symbol[], mapper: TypeMapper, mappingThisOnly: boolean): SymbolTable { - const result: SymbolTable = {}; + const result: SymbolTable = Map.create(); for (const symbol of symbols) { result[symbol.name] = mappingThisOnly && isIndependentMember(symbol) ? symbol : instantiateSymbol(symbol, mapper); } @@ -3980,7 +3972,7 @@ namespace ts { function addInheritedMembers(symbols: SymbolTable, baseSymbols: Symbol[]) { for (const s of baseSymbols) { - if (!hasProperty(symbols, s.name)) { + if (!Map.has(symbols, s.name)) { symbols[s.name] = s; } } @@ -4097,7 +4089,7 @@ namespace ts { } function createTupleTypeMemberSymbols(memberTypes: Type[]): SymbolTable { - const members: SymbolTable = {}; + const members: SymbolTable = Map.create(); for (let i = 0; i < memberTypes.length; i++) { const symbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "" + i); symbol.type = memberTypes[i]; @@ -4320,7 +4312,7 @@ namespace ts { function getPropertyOfObjectType(type: Type, name: string): Symbol { if (type.flags & TypeFlags.ObjectType) { const resolved = resolveStructuredTypeMembers(type); - if (hasProperty(resolved.members, name)) { + if (Map.has(resolved.members, name)) { const symbol = resolved.members[name]; if (symbolIsValue(symbol)) { return symbol; @@ -4453,8 +4445,8 @@ namespace ts { } function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: string): Symbol { - const properties = type.resolvedProperties || (type.resolvedProperties = {}); - if (hasProperty(properties, name)) { + const properties = type.resolvedProperties || (type.resolvedProperties = Map.create()); + if (Map.has(properties, name)) { return properties[name]; } const property = createUnionOrIntersectionProperty(type, name); @@ -4476,7 +4468,7 @@ namespace ts { type = getApparentType(type); if (type.flags & TypeFlags.ObjectType) { const resolved = resolveStructuredTypeMembers(type); - if (hasProperty(resolved.members, name)) { + if (Map.has(resolved.members, name)) { const symbol = resolved.members[name]; if (symbolIsValue(symbol)) { return symbol; @@ -4577,7 +4569,7 @@ namespace ts { function symbolsToArray(symbols: SymbolTable): Symbol[] { const result: Symbol[] = []; - for (const id in symbols) { + for (const id in symbols) if (Map.guard(symbols, id)) { if (!isReservedMemberName(id)) { result.push(symbols[id]); } @@ -5478,7 +5470,7 @@ namespace ts { function getLiteralTypeForText(flags: TypeFlags, text: string) { const map = flags & TypeFlags.StringLiteral ? stringLiteralTypes : numericLiteralTypes; - return hasProperty(map, text) ? map[text] : map[text] = createLiteralType(flags, text); + return Map.has(map, text) ? map[text] : map[text] = createLiteralType(flags, text); } function getTypeFromLiteralTypeNode(node: LiteralTypeNode): Type { @@ -6593,7 +6585,7 @@ namespace ts { } sourceStack[depth] = source; targetStack[depth] = target; - maybeStack[depth] = {}; + maybeStack[depth] = Map.create(); maybeStack[depth][id] = RelationComparisonResult.Succeeded; depth++; const saveExpandingFlags = expandingFlags; @@ -6624,7 +6616,7 @@ namespace ts { const maybeCache = maybeStack[depth]; // If result is definitely true, copy assumptions to global cache, else copy to next level up const destinationCache = (result === Ternary.True || depth === 0) ? relation : maybeStack[depth - 1]; - copyMap(maybeCache, destinationCache); + Map.assign(destinationCache, maybeCache); } else { // A false result goes straight into global cache (when something is false under assumptions it @@ -7234,7 +7226,7 @@ namespace ts { } function transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) { - const members: SymbolTable = {}; + const members: SymbolTable = Map.create(); for (const property of getPropertiesOfObjectType(type)) { const original = getTypeOfSymbol(property); const updated = f(original); @@ -7458,7 +7450,7 @@ namespace ts { let targetStack: Type[]; let depth = 0; let inferiority = 0; - const visited: Map = {}; + const visited = Map.create(); inferFromTypes(source, target); function isInProcess(source: Type, target: Type) { @@ -7592,7 +7584,7 @@ namespace ts { return; } const key = source.id + "," + target.id; - if (hasProperty(visited, key)) { + if (Map.has(visited, key)) { return; } visited[key] = true; @@ -8349,7 +8341,7 @@ namespace ts { // If we have previously computed the control flow type for the reference at // this flow loop junction, return the cached type. const id = getFlowNodeId(flow); - const cache = flowLoopCaches[id] || (flowLoopCaches[id] = {}); + const cache = flowLoopCaches[id] || (flowLoopCaches[id] = Map.create()); if (!key) { key = getFlowCacheKey(reference); } @@ -8514,14 +8506,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 = Map.get(typeofTypesByName, literal.text); if (targetType && isTypeSubtypeOf(targetType, type)) { return targetType; } } const facts = assumeTrue ? - getProperty(typeofEQFacts, literal.text) || TypeFacts.TypeofEQHostObject : - getProperty(typeofNEFacts, literal.text) || TypeFacts.TypeofNEHostObject; + Map.get(typeofEQFacts, literal.text) || TypeFacts.TypeofEQHostObject : + Map.get(typeofNEFacts, literal.text) || TypeFacts.TypeofNEHostObject; return getTypeWithFacts(type, facts); } @@ -9944,7 +9936,7 @@ namespace ts { // Grammar checking checkGrammarObjectLiteralExpression(node, inDestructuringPattern); - const propertiesTable: SymbolTable = {}; + const propertiesTable: SymbolTable = Map.create(); const propertiesArray: Symbol[] = []; const contextualType = getApparentTypeOfContextualType(node); const contextualTypeHasPattern = contextualType && contextualType.pattern && @@ -10035,7 +10027,7 @@ namespace ts { // type with those properties for which the binding pattern specifies a default value. if (contextualTypeHasPattern) { for (const prop of getPropertiesOfType(contextualType)) { - if (!hasProperty(propertiesTable, prop.name)) { + if (!Map.has(propertiesTable, prop.name)) { if (!(prop.flags & SymbolFlags.Optional)) { error(prop.valueDeclaration || (prop).bindingElement, Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value); @@ -10163,7 +10155,7 @@ namespace ts { for (const prop of props) { // Is there a corresponding property in the element attributes type? Skip checking of properties // that have already been assigned to, as these are not actually pushed into the resulting type - if (!hasProperty(nameTable, prop.name)) { + if (!Map.has(nameTable, prop.name)) { const targetPropSym = getPropertyOfType(elementAttributesType, prop.name); if (targetPropSym) { const msg = chainDiagnosticMessages(undefined, Diagnostics.Property_0_of_JSX_spread_attribute_is_not_assignable_to_target_property, prop.name); @@ -10485,7 +10477,7 @@ namespace ts { const targetAttributesType = getJsxElementAttributesType(node); - const nameTable: Map = {}; + const nameTable = Map.create(); // 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 @@ -10509,7 +10501,7 @@ namespace ts { const targetProperties = getPropertiesOfType(targetAttributesType); for (let i = 0; i < targetProperties.length; i++) { if (!(targetProperties[i].flags & SymbolFlags.Optional) && - !hasProperty(nameTable, targetProperties[i].name)) { + !Map.has(nameTable, targetProperties[i].name)) { error(node, Diagnostics.Property_0_is_missing_in_type_1, targetProperties[i].name, typeToString(targetAttributesType)); } @@ -13793,8 +13785,8 @@ namespace ts { function checkClassForDuplicateDeclarations(node: ClassLikeDeclaration) { const getter = 1, setter = 2, property = getter | setter; - const instanceNames: Map = {}; - const staticNames: Map = {}; + const instanceNames = Map.create(); + const staticNames = Map.create(); for (const member of node.members) { if (member.kind === SyntaxKind.Constructor) { for (const param of (member as ConstructorDeclaration).parameters) { @@ -13827,7 +13819,7 @@ namespace ts { } function addName(names: Map, location: Node, name: string, meaning: number) { - if (hasProperty(names, name)) { + if (Map.has(names, name)) { const prev = names[name]; if (prev & meaning) { error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); @@ -13843,7 +13835,7 @@ namespace ts { } function checkObjectTypeForDuplicateDeclarations(node: TypeLiteralNode | InterfaceDeclaration) { - const names: Map = {}; + const names = Map.create(); for (const member of node.members) { if (member.kind == SyntaxKind.PropertySignature) { let memberName: string; @@ -13857,7 +13849,7 @@ namespace ts { continue; } - if (hasProperty(names, memberName)) { + if (Map.has(names, memberName)) { error(member.symbol.valueDeclaration.name, Diagnostics.Duplicate_identifier_0, memberName); error(member.name, Diagnostics.Duplicate_identifier_0, memberName); } @@ -15068,23 +15060,21 @@ 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)); + for (const key in node.locals) if (Map.guard(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)); + } } } } @@ -15140,14 +15130,12 @@ 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); - } + for (const key in node.locals) if (Map.guard(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); } } } @@ -16156,7 +16144,7 @@ namespace ts { else { const identifierName = (catchClause.variableDeclaration.name).text; const locals = catchClause.block.locals; - if (locals && hasProperty(locals, identifierName)) { + if (locals && Map.has(locals, identifierName)) { const localSymbol = locals[identifierName]; if (localSymbol && (localSymbol.flags & SymbolFlags.BlockScopedVariable) !== 0) { grammarErrorOnNode(localSymbol.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, identifierName); @@ -16577,14 +16565,14 @@ namespace ts { return true; } - const seen: Map<{ prop: Symbol; containingType: Type }> = {}; + const seen = Map.create<{ prop: Symbol; containingType: Type }>(); forEach(resolveDeclaredMembers(type).declaredProperties, p => { seen[p.name] = { prop: p, containingType: type }; }); let ok = true; for (const base of baseTypes) { const properties = getPropertiesOfObjectType(getTypeWithThisArgument(base, type.thisType)); for (const prop of properties) { - if (!hasProperty(seen, prop.name)) { + if (!Map.has(seen, prop.name)) { seen[prop.name] = { prop: prop, containingType: base }; } else { @@ -17327,7 +17315,7 @@ namespace ts { } function hasExportedMembers(moduleSymbol: Symbol) { - for (const id in moduleSymbol.exports) { + for (const id in moduleSymbol.exports) if (Map.guard(moduleSymbol.exports, id)) { if (id !== "export=") { return true; } @@ -17348,7 +17336,7 @@ namespace ts { } // Checks for export * conflicts const exports = getExportsOfModule(moduleSymbol); - for (const id in exports) { + for (const id in exports) if (Map.guard(exports, id)) { if (id === "__export") { continue; } @@ -17647,7 +17635,7 @@ namespace ts { } function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] { - const symbols: SymbolTable = {}; + const symbols: SymbolTable = Map.create(); let memberFlags: NodeFlags = 0; if (isInsideWithStatementBody(location)) { @@ -17725,7 +17713,7 @@ namespace ts { // We will copy all symbol regardless of its reserved name because // symbolsToArray will check whether the key is a reserved name and // it will not copy symbol with reserved name to the array - if (!hasProperty(symbols, id)) { + if (!Map.has(symbols, id)) { symbols[id] = symbol; } } @@ -17733,7 +17721,7 @@ namespace ts { function copySymbols(source: SymbolTable, meaning: SymbolFlags): void { if (meaning) { - for (const id in source) { + for (const id in source) if (Map.guard(source, id)) { const symbol = source[id]; copySymbol(symbol, meaning); } @@ -18144,7 +18132,7 @@ namespace ts { const propsByName = createSymbolTable(getPropertiesOfType(type)); if (getSignaturesOfType(type, SignatureKind.Call).length || getSignaturesOfType(type, SignatureKind.Construct).length) { forEach(getPropertiesOfType(globalFunctionType), p => { - if (!hasProperty(propsByName, p.name)) { + if (!Map.has(propsByName, p.name)) { propsByName[p.name] = p; } }); @@ -18201,7 +18189,7 @@ namespace ts { // otherwise - check if at least one export is value symbolLinks.exportsSomeValue = hasExportAssignment ? !!(moduleSymbol.flags & SymbolFlags.Value) - : forEachValue(getExportsOfModule(moduleSymbol), isValue); + : Map.forEach(getExportsOfModule(moduleSymbol), isValue); } return symbolLinks.exportsSomeValue; @@ -18492,7 +18480,7 @@ namespace ts { } function hasGlobalName(name: string): boolean { - return hasProperty(globals, name); + return Map.has(globals, name); } function getReferencedValueSymbol(reference: Identifier): Symbol { @@ -18515,10 +18503,7 @@ 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; - } + for (const key in resolvedTypeReferenceDirectives) if (Map.guard(resolvedTypeReferenceDirectives, key)) { const resolvedDirective = resolvedTypeReferenceDirectives[key]; if (!resolvedDirective) { continue; @@ -19307,7 +19292,7 @@ namespace ts { } function checkGrammarObjectLiteralExpression(node: ObjectLiteralExpression, inDestructuring: boolean) { - const seen: Map = {}; + const seen = Map.create(); const Property = 1; const GetAccessor = 2; const SetAccessor = 4; @@ -19369,7 +19354,7 @@ namespace ts { continue; } - if (!hasProperty(seen, effectiveName)) { + if (!Map.has(seen, effectiveName)) { seen[effectiveName] = currentKind; } else { @@ -19393,7 +19378,7 @@ namespace ts { } function checkGrammarJsxElement(node: JsxOpeningLikeElement) { - const seen: Map = {}; + const seen = Map.create(); for (const attr of node.attributes) { if (attr.kind === SyntaxKind.JsxSpreadAttribute) { continue; @@ -19401,7 +19386,7 @@ namespace ts { const jsxAttr = (attr); const name = jsxAttr.name; - if (!hasProperty(seen, name.text)) { + if (!Map.has(seen, name.text)) { seen[name.text] = true; } else { diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 7e2c6eb8d334e..def9d74838b84 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -470,8 +470,8 @@ namespace ts { return optionNameMapCache; } - const optionNameMap: Map = {}; - const shortOptionNames: Map = {}; + const optionNameMap = Map.create(); + const shortOptionNames = Map.create(); forEach(optionDeclarations, option => { optionNameMap[option.name.toLowerCase()] = option; if (option.shortName) { @@ -486,10 +486,9 @@ namespace ts { /* @internal */ export function createCompilerDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType): Diagnostic { const namesOfType: string[] = []; - forEachKey(opt.type, key => { + for (const key in opt.type) if (MapLike.guard(opt.type, key)) { namesOfType.push(` '${key}'`); - }); - + } return createCompilerDiagnostic(Diagnostics.Argument_for_0_option_must_be_Colon_1, `--${opt.name}`, namesOfType); } @@ -497,7 +496,7 @@ namespace ts { export function parseCustomTypeOption(opt: CommandLineOptionOfCustomType, value: string, errors: Diagnostic[]) { const key = trimString((value || "")).toLowerCase(); const map = opt.type; - if (hasProperty(map, key)) { + if (MapLike.has(map, key)) { return map[key]; } else { @@ -551,11 +550,11 @@ namespace ts { s = s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1).toLowerCase(); // Try to translate short option names to their full equivalents. - if (hasProperty(shortOptionNames, s)) { + if (Map.has(shortOptionNames, s)) { s = shortOptionNames[s]; } - if (hasProperty(optionNameMap, s)) { + if (Map.has(optionNameMap, s)) { const opt = optionNameMap[s]; if (opt.isTSConfigOnly) { @@ -703,7 +702,7 @@ namespace ts { export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions: CompilerOptions = {}, configFileName?: string): ParsedCommandLine { const errors: Diagnostic[] = []; const compilerOptions: CompilerOptions = convertCompilerOptionsFromJsonWorker(json["compilerOptions"], basePath, errors, configFileName); - const options = extend(existingOptions, compilerOptions); + const options = MapLike.extend(existingOptions, compilerOptions); const typingOptions: TypingOptions = convertTypingOptionsFromJsonWorker(json["typingOptions"], basePath, errors, configFileName); options.configFilePath = configFileName; @@ -721,7 +720,7 @@ namespace ts { function getFileNames(errors: Diagnostic[]): ExpandResult { let fileNames: string[]; - if (hasProperty(json, "files")) { + if (MapLike.has(json, "files")) { if (isArray(json["files"])) { fileNames = json["files"]; } @@ -731,7 +730,7 @@ namespace ts { } let includeSpecs: string[]; - if (hasProperty(json, "include")) { + if (MapLike.has(json, "include")) { if (isArray(json["include"])) { includeSpecs = json["include"]; } @@ -741,7 +740,7 @@ namespace ts { } let excludeSpecs: string[]; - if (hasProperty(json, "exclude")) { + if (MapLike.has(json, "exclude")) { if (isArray(json["exclude"])) { excludeSpecs = json["exclude"]; } @@ -749,7 +748,7 @@ namespace ts { errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "exclude", "Array")); } } - else if (hasProperty(json, "excludes")) { + else if (MapLike.has(json, "excludes")) { errors.push(createCompilerDiagnostic(Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); } else { @@ -808,10 +807,10 @@ namespace ts { return; } - const optionNameMap = arrayToMap(optionDeclarations, opt => opt.name); + const optionNameMap = Map.from(optionDeclarations, opt => opt.name); - for (const id in jsonOptions) { - if (hasProperty(optionNameMap, id)) { + for (const id in jsonOptions) if (MapLike.guard(jsonOptions, id)) { + if (Map.has(optionNameMap, id)) { const opt = optionNameMap[id]; defaultOptions[opt.name] = convertJsonOption(opt, jsonOptions[id], basePath, errors); } @@ -848,7 +847,7 @@ namespace ts { function convertJsonOptionOfCustomType(opt: CommandLineOptionOfCustomType, value: string, errors: Diagnostic[]) { const key = value.toLowerCase(); - if (hasProperty(opt.type, key)) { + if (MapLike.has(opt.type, key)) { return opt.type[key]; } else { @@ -958,12 +957,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 = Map.create(); // 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 = Map.create(); if (include) { include = validateSpecs(include, errors, /*allowTrailingRecursion*/ false); @@ -977,7 +976,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. @@ -1011,14 +1010,14 @@ namespace ts { removeWildcardFilesWithLowerPriorityExtension(file, wildcardFileMap, supportedExtensions, keyMapper); const key = keyMapper(file); - if (!hasProperty(literalFileMap, key) && !hasProperty(wildcardFileMap, key)) { + if (!Map.has(literalFileMap, key) && !Map.has(wildcardFileMap, key)) { wildcardFileMap[key] = file; } } } - const literalFiles = reduceProperties(literalFileMap, addFileToOutput, []); - const wildcardFiles = reduceProperties(wildcardFileMap, addFileToOutput, []); + const literalFiles = Map.reduce(literalFileMap, addFileToOutput, []); + const wildcardFiles = Map.reduce(wildcardFileMap, addFileToOutput, []); wildcardFiles.sort(host.useCaseSensitiveFileNames ? compareStrings : compareStringsCaseInsensitive); return { fileNames: literalFiles.concat(wildcardFiles), @@ -1063,7 +1062,7 @@ namespace ts { // /a/b/a?z - Watch /a/b directly to catch any new file matching a?z const rawExcludeRegex = getRegularExpressionForWildcard(exclude, path, "exclude"); const excludeRegex = rawExcludeRegex && new RegExp(rawExcludeRegex, useCaseSensitiveFileNames ? "" : "i"); - const wildcardDirectories: Map = {}; + const wildcardDirectories = Map.create(); if (include !== undefined) { const recursiveKeys: string[] = []; for (const file of include) { @@ -1076,7 +1075,7 @@ namespace ts { if (match) { const key = useCaseSensitiveFileNames ? match[0] : match[0].toLowerCase(); const flags = watchRecursivePattern.test(name) ? WatchDirectoryFlags.Recursive : WatchDirectoryFlags.None; - const existingFlags = getProperty(wildcardDirectories, key); + const existingFlags = Map.get(wildcardDirectories, key); if (existingFlags === undefined || existingFlags < flags) { wildcardDirectories[key] = flags; if (flags === WatchDirectoryFlags.Recursive) { @@ -1087,12 +1086,10 @@ namespace ts { } // Remove any subpaths under an existing recursively watched directory. - for (const key in wildcardDirectories) { - if (hasProperty(wildcardDirectories, key)) { - for (const recursiveKey of recursiveKeys) { - if (key !== recursiveKey && containsPath(recursiveKey, key, path, !useCaseSensitiveFileNames)) { - delete wildcardDirectories[key]; - } + for (const key in wildcardDirectories) if (Map.guard(wildcardDirectories, key)) { + for (const recursiveKey of recursiveKeys) { + if (key !== recursiveKey && containsPath(recursiveKey, key, path, !useCaseSensitiveFileNames)) { + delete wildcardDirectories[key]; } } } @@ -1115,7 +1112,7 @@ namespace ts { for (let i = ExtensionPriority.Highest; i < adjustedExtensionPriority; i++) { const higherPriorityExtension = extensions[i]; const higherPriorityPath = keyMapper(changeExtension(file, higherPriorityExtension)); - if (hasProperty(literalFiles, higherPriorityPath) || hasProperty(wildcardFiles, higherPriorityPath)) { + if (Map.has(literalFiles, higherPriorityPath) || Map.has(wildcardFiles, higherPriorityPath)) { return true; } } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 549410159b035..a5994f47a0a7f 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 = {}; + let files = Map.create(); return { get, set, @@ -31,7 +31,7 @@ namespace ts { }; function forEachValueInMap(f: (key: Path, value: T) => void) { - for (const key in files) { + for (const key in files) if (Map.guard(files, key)) { f(key, files[key]); } } @@ -46,7 +46,7 @@ namespace ts { } function contains(path: Path) { - return hasProperty(files, toKey(path)); + return Map.has(files, toKey(path)); } function remove(path: Path) { @@ -55,7 +55,7 @@ namespace ts { } function clear() { - files = {}; + files = Map.create(); } function toKey(path: Path): string { @@ -322,120 +322,531 @@ namespace ts { 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); + /** + * Utilitiy functions for MapLike objects. + */ + export namespace MapLike { + /** + * Tests whether the provided key is present in the map-like. + * + * @param map A map-like. + * @param key A key in the map-like. + */ + export function has(map: MapLike, key: string | number) { + return hasOwnProperty.call(map, 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; + /** + * Gets the value of the entry in the map-like with the provided key. + * + * @param map A map-like. + * @param key A key in the map-like. + */ + export function get(map: MapLike, key: string | number) { + return hasOwnProperty.call(map, key) ? map[key] : undefined; + } + /** + * Guards own-properties during for..in enumeration of a map-like. + * + * @param map A map-like. + * @param key A key in the map-like. + */ + export function guard(map: MapLike, key: string | number) { + return hasOwnProperty.call(map, key); + } + /** + * Creates an array for each key in a map. + * + * @param map A map. + */ + export function keys(map: MapLike) { + const keys: string[] = []; + for (const key in map) if (guard(map, key)) { + keys.push(key); } + return keys; } - return true; - } - - export function clone(object: T): T { - const result: any = {}; - for (const id in object) { - result[id] = (object)[id]; + /** + * Calculates the number of entries in a map. + * + * @param map A map. + */ + export function size(map: MapLike) { + let count = 0; + for (const key in map) if (guard(map, key)) { + count++; + } + return count; } - 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]; + /** + * Creates a shallow clone of a map. + * + * @param map A map. + */ + export function clone>(map: T) { + const clone = {}; + for (const key in map) if (guard(map, key)) { + clone[key] = map[key]; + } + return clone; } - for (const id in second) { - if (!hasProperty(result, id)) { - (result as any)[id] = second[id]; + /** + * Invokes a callback for each element in the map-like. + * + * @param map A map-like. + * @param callback A callback to execute for each entry. + */ + export function forEach(map: MapLike, callback: (value: T, key: string) => U) { + for (const key in map) if (guard(map, key)) { + const result = callback(map[key], key); + if (result) { + return result; + } } } - 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; + /** + * 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 reduce(map: MapLike, callback: (aggregate: U, value: T, key: string) => U, initial: U) { + let result = initial; + for (const key in map) if (guard(map, key)) { + result = callback(result, map[key], key); + } + return result; } - 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; + /** + * Creates a new object by merging the properties of two objects. + */ + export function extend, T2 extends MapLike<{}>>(first: T1, second: T2): T1 & T2 { + const result: T1 & T2 = {}; + for (const id in first) if (guard(first, id)) { + (result as any)[id] = first[id]; + } + for (const id in second) if (guard(second, id)) { + if (!has(result, id)) { + (result as any)[id] = second[id]; + } + } + return result; } - 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]; + /** + * Test for shallow equality of two map-likes. + * + * @param left A map-like. + * @param right A map-like. + */ + export function equals(left: MapLike, right: MapLike) { + if (left === right) return true; + if (!left || !right) return false; + for (const key in left) if (guard(left, key)) { + if (!has(right, key)) return false; + if (get(left, key) !== get(right, key)) return false; + } + for (const key in right) if (guard(right, key)) { + if (!has(left, key)) return false; + } + return true; } } /** - * Creates a map from the elements of an array. - * - * @param array the array of input elements. - * @param makeKey a function that produces a key for a given element. - * - * This function makes no effort to avoid collisions; if any two elements produce - * the same key with the given 'makeKey' function, then the element with the higher - * index in the array will be the one associated with the produced key. - */ - export function arrayToMap(array: T[], makeKey: (value: T) => string): Map { - 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. + * Utility functions for Map objects. */ - 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)); + export namespace Map { + const createCore = Object.create + ? () => >Object.create(null) // tslint:disable-line:no-null-keyword + : () => >{}; + const freezeCore = Object.create + ? (map: Map) => >Object.freeze(map) + : (map: Map) => >map; + const hasCore = Object.create + ? (map: Map, key: string | number) => key in map // tslint:disable-line:no-in-operator + : MapLike.has; + const getCore = Object.create + ? (map: Map, key: string | number) => map[key] + : MapLike.get; + const guardCore = Object.create + ? (map: Map, key: string | number) => true + : MapLike.guard; + class ObjectModeHelper {} + /** + * Creates a map. + * + * This map will be created in "dictionary" mode. + * + * @param template An optional map-like used to populate the map. + */ + export function create(template?: MapLike) { + return assign(setDictionaryMode(createCore()), template); + } + /** + * Creates a read-only map. + * + * This map will be created in "fast" mode and ideally is frozen. + * + * @param template An optional map-like used to populate the map. + */ + export function createReadOnly(template?: MapLike) { + return freezeCore(setObjectMode(create(template))); + } + /** + * Creates a map from an array. + * + * @param array An array. + * @param keySelector A callback used to generate keys for elements in the array. + */ + export function from(array: T[], keySelector: (value: T) => string): Map; + /** + * Creates a map from an array. + * + * @param array An array. + * @param keySelector A callback used to generate keys for elements in the array. + * @param elementSelector A callback used to generate values for elements in the array. + */ + export function from(array: T[], keySelector: (value: T) => string | number, elementSelector: (value: T) => U): Map; + export function from(array: T[], keySelector: (value: T) => string | number, elementSelector?: (value: T) => U): Map { + const map = create(); + for (const value of array) { + map[keySelector(value)] = elementSelector ? elementSelector(value) : value; + } + return >map; + } + /** + * Tests whether the provided key is present in the map. + * + * @param map A map. + * @param key A key in the map. + */ + export function has(map: Map, key: string | number) { + return hasCore(map, key); + } + /** + * Gets the value of the entry in the map with the provided key. + * + * @param map A map. + * @param key A key in the map. + */ + export function get(map: Map, key: string | number) { + return getCore(map, key); + } + /** + * Gets the value of the entry in the map with the provided key, or sets a new entry. + * + * @param map A map. + * @param key A key in the map. + * @param value A value to add to the map for a missing entry. + */ + export function getOrAdd(map: Map, key: string | number, value: T) { + return hasCore(map, key) ? map[key] : (map[key] = value); + } + /** + * Gets the value of the entry in the map with the provided key, or creates a new entry. + * + * @param map A map. + * @param key A key in the map. + * @param factory A callback used to create a new entry. + */ + export function getOrCreate(map: Map, key: string | number, factory: (key: string) => T) { + return hasCore(map, key) ? map[key] : (map[key] = factory(String(key))); + } + /** + * Gets the value of the entry in the map with the provided key, or returns a default value. + * + * @param map A map. + * @param key A key in the map. + * @param defaultValue A default value to return when the key is not present. + */ + export function getOrDefault(map: Map, key: string | number, defaultValue: T) { + return hasCore(map, key) ? map[key] : defaultValue; + } + /** + * Guards own-properties during for..in enumeration of a map. + * + * @param map A map. + * @param key A key in the map. + */ + export function guard(map: Map, key: string | number) { + return guardCore(map, key); + } + /** + * Creates an array for each key in a map. + * + * @param map A map. + */ + export function keys(map: Map) { + const keys: string[] = []; + for (const key in map) if (guard(map, key)) { + keys.push(key); + } + return keys; + } + /** + * Creates an array for each value in a map. + * + * @param map A map. + */ + export function values(map: Map) { + const values: T[] = []; + for (const key in map) if (guard(map, key)) { + values.push(map[key]); + } + return values; + } + /** + * Calculates the number of entries in a map. + * + * @param map A map. + */ + export function size(map: Map) { + let count = 0; + for (const key in map) if (guard(map, key)) { + count++; + } + return count; + } + /** + * Tests whether a map contains any entries. + * + * @param map A map. + */ + export function isEmpty(map: Map) { + for (const key in map) if (guard(map, key)) { + return false; + } + return true; + } + /** + * Assigns entries in a target map for each own entry in a source map-like. + * + * @param target The target map. + * @param source The source map-like. + */ + export function assign(target: Map, source: MapLike) { + for (const key in source) if (MapLike.guard(source, key)) { + target[key] = source[key]; + } + return target; + } + /** + * Creates a shallow clone of a map. + * + * @param map A map. + */ + export function clone>(map: T) { + return assign(create<{}>(), map); + } + /** + * Creates a shallow, read-only clone of a map. + * + * @param map A map. + */ + export function cloneReadOnly(map: Map) { + return freezeCore(setObjectMode(clone(map))); + } + /** + * Invokes a callback for each element in the map. + * + * @param map A map. + * @param callback A callback to execute for each entry. + */ + export function forEach(map: Map, callback: (value: T, key: string) => U) { + for (const key in map) if (guard(map, key)) { + const result = callback(map[key], key); + if (result) { + return result; } } } + /** + * Creates a copy of a map whose values have been mapped to another value. + * + * @param map A map. + * @param predicate A callback to execute for each entry. + */ + export function map(map: Map, selector: (value: T, key: string) => U) { + const result = create(); + for (const key in map) if (guard(map, key)) { + const value = map[key]; + result[key] = selector(value, key); + } + return result; + } + /** + * Creates a copy of a map whose entries have been mapped to another entry. + * + * @param map A map. + * @param predicate A callback to execute for each entry. + */ + export function mapPairs(map: Map, selector: (value: T, key: string) => [string | number, U]) { + const result = create(); + for (const key in map) if (guard(map, key)) { + const value = map[key]; + const pair = selector(value, key); + result[pair[0]] = pair[1]; + } + return result; + } + /** + * Creates a copy of a map with only the properties that match the supplied predicate. + * + * @param map A map. + * @param predicate A callback to execute for each entry. + */ + export function filter(map: Map, predicate: (value: T, key: string) => boolean) { + const result = create(); + for (const key in map) if (guard(map, key)) { + const value = map[key]; + if (predicate(value, key)) result[key] = value; + } + return result; + } + /** + * Finds the value in the map for the entry that matches the supplied predicate. + * + * @param map A map. + * @param predicate A callback to execute for each entry. + */ + export function find(map: Map, predicate: (value: T, key: string) => boolean) { + for (const key in map) if (guard(map, key)) { + const value = map[key]; + if (predicate(value, key)) return value; + } + } + /** + * Finds the key in the map for the entry that matches the supplied predicate. + * + * @param map A map. + * @param predicate A callback to execute for each entry. + */ + export function findKey(map: Map, predicate: (value: T, key: string) => boolean) { + for (const key in map) if (guard(map, key)) { + const value = map[key]; + if (predicate(value, key)) return key; + } + } + /** + * Finds the key in the map for the entry that matches the supplied predicate. + * + * @param map A map. + * @param predicate A callback to execute for each entry. + */ + export function findPair(map: Map, predicate: (value: T, key: string) => boolean) { + for (const key in map) if (guard(map, key)) { + const value = map[key]; + if (predicate(value, key)) return <[string, T]>[key, value]; + } + } + /** + * Gets the key for a value in the map. + * + * @param map A map. + * @param value A value. + */ + export function keyFor(map: Map, value: T) { + for (const key in map) if (guard(map, key)) { + if (value === map[key]) return key; + } + } + /** + * Tests whether the provided value is present in the map. + * + * @param map A map. + * @param value A value. + */ + export function includes(map: Map, value: T) { + for (const key in map) if (guard(map, key)) { + if (value === map[key]) return true; + } + } + /** + * Tests whether at least one entry in a map matches the supplied predicate. + * + * @param map A map. + * @param predicate A callback to execute for each entry. + */ + export function some(map: Map, predicate?: (value: T, key: string) => boolean): boolean { + for (const key in map) if (guard(map, key)) { + if (!predicate || predicate(map[key], key)) return true; + } + return false; + } + /** + * Tests whether every entry in a map matches the supplied predicate. + * + * @param map A map. + * @param predicate A callback to execute for each entry. + */ + export function every(map: Map, predicate: (value: T, key: string) => boolean): boolean { + for (const key in map) if (guard(map, key)) { + if (!predicate(map[key], key)) return false; + } + return true; + } + /** + * 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 reduce(map: Map, callback: (aggregate: U, value: T, key: string) => U, initial: U) { + let result = initial; + for (const key in map) if (guard(map, key)) { + result = callback(result, map[key], key); + } + return result; + } + /** + * Test for shallow equality of two maps. + * + * @param left A map. + * @param right A map. + */ + export function equals(left: Map, right: Map) { + if (left === right) return true; + if (!left || !right) return false; + for (const key in left) if (guard(left, key)) { + if (!has(right, key)) return false; + if (get(left, key) !== get(right, key)) return false; + } + for (const key in right) if (guard(right, key)) { + if (!has(left, key)) return false; + } + return true; + } + function setDictionaryMode(map: Map) { + (map).__DICTIONARY_MODE__ = 1; + delete (map).__DICTIONARY_MODE__; + return map; + } + function setObjectMode(map: Map) { + const savedPrototype = ObjectModeHelper.prototype; + ObjectModeHelper.prototype = map; + use(new ObjectModeHelper()); + ObjectModeHelper.prototype = savedPrototype; + return map; + } + function use(value: any) { + // conditional return avoids unreachable code detection. + if (!0) return; + // direct eval prevents inlining on v8. + eval(undefined); + } + } - return result; + const cloneCore = Object.create + ? (object: T) => Object.create(Object.getPrototypeOf(object)) + : (object: T) => {}; + + export function clone(object: T): T { + const result: any = cloneCore(object); + for (const id in object) if (hasOwnProperty.call(object, id)) { + result[id] = (object)[id]; + } + return result; } /** @@ -462,12 +873,12 @@ namespace ts { return text.replace(/{(\d+)}/g, (match, index?) => args[+index + baseIndex]); } - export let localizedDiagnosticMessages: Map = undefined; + export let localizedDiagnosticMessages: MapLike = undefined; export function getLocaleSpecificMessage(message: DiagnosticMessage) { - return localizedDiagnosticMessages && localizedDiagnosticMessages[message.key] - ? localizedDiagnosticMessages[message.key] - : message.message; + return localizedDiagnosticMessages + && MapLike.get(localizedDiagnosticMessages, message.key) + || message.message; } export function createFileDiagnostic(file: SourceFile, start: number, length: number, message: DiagnosticMessage, ...args: any[]): Diagnostic; diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index c93784cd9e0c3..79d42a984e8f5 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -156,10 +156,8 @@ namespace ts { }); if (usedTypeDirectiveReferences) { - for (const directive in usedTypeDirectiveReferences) { - if (hasProperty(usedTypeDirectiveReferences, directive)) { - referencesOutput += `/// ${newLine}`; - } + for (const directive in usedTypeDirectiveReferences) if (Map.guard(usedTypeDirectiveReferences, directive)) { + referencesOutput += `/// ${newLine}`; } } @@ -269,10 +267,10 @@ namespace ts { } if (!usedTypeDirectiveReferences) { - usedTypeDirectiveReferences = {}; + usedTypeDirectiveReferences = Map.create(); } for (const directive of typeReferenceDirectives) { - if (!hasProperty(usedTypeDirectiveReferences, directive)) { + if (!Map.has(usedTypeDirectiveReferences, directive)) { usedTypeDirectiveReferences[directive] = directive; } } @@ -537,14 +535,14 @@ namespace ts { // do not need to keep track of created temp names. function getExportDefaultTempVariableName(): string { const baseName = "_default"; - if (!hasProperty(currentIdentifiers, baseName)) { + if (!Map.has(currentIdentifiers, baseName)) { return baseName; } let count = 0; while (true) { count++; const name = baseName + "_" + count; - if (!hasProperty(currentIdentifiers, name)) { + if (!Map.has(currentIdentifiers, name)) { return name; } } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index c8bd8072b2400..b05ad943b961e 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -24,7 +24,7 @@ namespace ts { Return = 1 << 3 } - const entities: Map = { + const entities = Map.createReadOnly({ "quot": 0x0022, "amp": 0x0026, "apos": 0x0027, @@ -278,7 +278,7 @@ namespace ts { "clubs": 0x2663, "hearts": 0x2665, "diams": 0x2666 - }; + }); // Flags enum to track count of temp variables and a few dedicated names const enum TempFlags { @@ -407,7 +407,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge function isUniqueLocalName(name: string, container: Node): boolean { for (let node = container; isNodeDescendentOf(node, container); node = node.nextContainer) { - if (node.locals && hasProperty(node.locals, name)) { + if (node.locals && Map.has(node.locals, 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)) { return false; @@ -430,8 +430,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge * - label marker - return value that should be interpreted by calling code as 'jump to