From ddb10e1085c36dae5f3eafd54be32615c692f2c7 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Wed, 3 Aug 2016 13:59:44 -0700 Subject: [PATCH 1/6] Add SMap and NMap data structures, which use the global Map type where available, to replace the old Map, which is now ObjMap. --- Jakefile.js | 2 + src/compiler/binder.ts | 45 +- src/compiler/checker.ts | 628 +++++++------- src/compiler/commandLineParser.ts | 191 ++--- src/compiler/core.ts | 368 +-------- src/compiler/dataStructures.ts | 768 ++++++++++++++++++ src/compiler/declarationEmitter.ts | 8 +- src/compiler/emitter.ts | 43 +- src/compiler/parser.ts | 7 +- src/compiler/performance.ts | 28 +- src/compiler/program.ts | 81 +- src/compiler/scanner.ts | 273 ++++--- src/compiler/sys.ts | 43 +- src/compiler/tsc.ts | 91 +-- src/compiler/tsconfig.json | 1 + src/compiler/types.ts | 43 +- src/compiler/utilities.ts | 112 +-- src/harness/compilerRunner.ts | 32 +- src/harness/fourslash.ts | 76 +- src/harness/harness.ts | 34 +- src/harness/harnessLanguageService.ts | 14 +- src/harness/loggedIO.ts | 10 +- src/harness/projectsRunner.ts | 18 +- src/harness/tsconfig.json | 2 +- .../unittests/cachingInServerLSHost.ts | 21 +- src/harness/unittests/matchFiles.ts | 100 +-- src/harness/unittests/moduleResolution.ts | 159 ++-- .../unittests/reuseProgramStructure.ts | 58 +- src/harness/unittests/session.ts | 20 +- .../unittests/tsserverProjectSystem.ts | 38 +- src/server/client.ts | 12 +- src/server/editorServices.ts | 49 +- src/server/session.ts | 122 +-- src/services/formatting/rules.ts | 2 +- src/services/jsTyping.ts | 36 +- src/services/navigateTo.ts | 51 +- src/services/navigationBar.ts | 8 +- src/services/patternMatcher.ts | 8 +- src/services/services.ts | 126 +-- src/services/shims.ts | 8 +- src/services/signatureHelp.ts | 22 +- .../baselines/reference/APISample_watcher.js | 2 +- .../computedPropertyNames10_ES5.types | 4 +- .../computedPropertyNames10_ES6.types | 4 +- .../computedPropertyNames11_ES5.types | 4 +- .../computedPropertyNames11_ES6.types | 4 +- .../computedPropertyNames4_ES5.types | 4 +- .../computedPropertyNames4_ES6.types | 4 +- ...utedPropertyNamesContextualType6_ES5.types | 2 +- ...utedPropertyNamesContextualType6_ES6.types | 2 +- ...ntiationAssignmentWithIndexingOnLHS3.types | 16 +- ...rConstrainsPropertyDeclarations.errors.txt | 4 +- ...ConstrainsPropertyDeclarations2.errors.txt | 4 +- .../numericIndexerConstraint5.errors.txt | 4 +- .../objectLiteralIndexerErrors.errors.txt | 8 +- .../reference/objectLiteralIndexers.types | 10 +- ...ctTypeWithStringNamedNumericProperty.types | 30 +- ...rConstrainsPropertyDeclarations.errors.txt | 8 +- tests/cases/compiler/APISample_watcher.ts | 2 +- 59 files changed, 2029 insertions(+), 1845 deletions(-) create mode 100644 src/compiler/dataStructures.ts 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/src/compiler/binder.ts b/src/compiler/binder.ts index 8d8666a67abac..1cf2fe2ee6837 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: SSet; 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 SSet(); 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 SMap(); } if (symbolFlags & SymbolFlags.HasMembers && !symbol.members) { - symbol.members = {}; + symbol.members = new SMap(); } 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 = getOrUpdateMap(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 SMap(); } 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 = singletonMap(symbol.name, symbol); } function bindObjectLiteralExpression(node: ObjectLiteralExpression) { @@ -1409,7 +1407,7 @@ namespace ts { } if (inStrictMode) { - const seen: Map = {}; + const seen = new SMap(); 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 SMap(); 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 SMap(); 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 SMap(); // 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 SMap(); // 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 alreadyExistingPrototypeSymbol = symbol.exports.get(prototypeSymbol.name); + if (alreadyExistingPrototypeSymbol) { if (node.name) { node.name.parent = node; } - file.bindDiagnostics.push(createDiagnosticForNode(symbol.exports[prototypeSymbol.name].declarations[0], + file.bindDiagnostics.push(createDiagnosticForNode(alreadyExistingPrototypeSymbol.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..fa4f5e7513181 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 SMap(); 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 SMap(); + const unionTypes = new SMap(); + const intersectionTypes = new SMap(); + const stringLiteralTypes = new SMap(); + const numericLiteralTypes = new SMap(); 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 SMap(); 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 SMap(); /** * 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: SMap[] = []; 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 SMap([ + ["string", TypeFacts.TypeofEQString], + ["number", TypeFacts.TypeofEQNumber], + ["boolean", TypeFacts.TypeofEQBoolean], + ["symbol", TypeFacts.TypeofEQSymbol], + ["undefined", TypeFacts.EQUndefined], + ["object", TypeFacts.TypeofEQObject], + ["function", TypeFacts.TypeofEQFunction] + ]); + + const typeofNEFacts = new SMap([ + ["string", TypeFacts.TypeofNEString], + ["number", TypeFacts.TypeofNENumber], + ["boolean", TypeFacts.TypeofNEBoolean], + ["symbol", TypeFacts.TypeofNESymbol], + ["undefined", TypeFacts.NEUndefined], + ["object", TypeFacts.TypeofNEObject], + ["function", TypeFacts.TypeofNEFunction] + ]); + + const typeofTypesByName = new SMap([ + ["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 SMap(); 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 = SMap; + const subtypeRelation: Relation = new SMap(); + const assignableRelation: Relation = new SMap(); + const comparableRelation: Relation = new SMap(); + const identityRelation: Relation = new SMap(); // 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 = singletonMap(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 SMap(); mergeSymbolTable(target.members, source.members); } if (source.exports) { - if (!target.exports) target.exports = {}; + if (!target.exports) target.exports = new SMap(); 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 SMap(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?: SMap, 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 SMap(); + const lookupTable = new SMap(); 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 findInMap(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; @@ -3121,7 +3123,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 SMap(); let hasComputedProperties = false; forEach(pattern.elements, e => { const name = e.propertyName || e.name; @@ -3136,7 +3138,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 +3711,7 @@ namespace ts { type.typeParameters = concatenate(outerTypeParameters, localTypeParameters); type.outerTypeParameters = outerTypeParameters; type.localTypeParameters = localTypeParameters; - (type).instantiations = {}; - (type).instantiations[getTypeListId(type.typeParameters)] = type; + (type).instantiations = singletonMap(getTypeListId(type.typeParameters), type); (type).target = type; (type).typeArguments = type.typeParameters; type.thisType = createType(TypeFlags.TypeParameter | TypeFlags.ThisType); @@ -3751,8 +3752,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 = singletonMap(getTypeListId(links.typeParameters), type); } } else { @@ -3772,7 +3772,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 +3795,15 @@ namespace ts { enumType.symbol = symbol; if (enumHasLiteralMembers(symbol)) { const memberTypeList: Type[] = []; - const memberTypes: Map = {}; + const memberTypes = new NMap(); 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 +3816,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 +3828,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 +3958,20 @@ namespace ts { } function createSymbolTable(symbols: Symbol[]): SymbolTable { - const result: SymbolTable = {}; - for (const symbol of symbols) { - result[symbol.name] = symbol; - } - return result; + return createMapFromValues(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 createMapFromArray(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 +3979,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 +4083,12 @@ namespace ts { } function createTupleTypeMemberSymbols(memberTypes: Type[]): SymbolTable { - const members: SymbolTable = {}; + const members: SymbolTable = new SMap(); 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 +4232,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 +4247,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 +4306,9 @@ namespace ts { function getPropertyOfObjectType(type: Type, name: string): Symbol { if (type.flags & TypeFlags.ObjectType) { const resolved = resolveStructuredTypeMembers(type); - if (hasProperty(resolved.members, name)) { - const symbol = resolved.members[name]; - if (symbolIsValue(symbol)) { - return symbol; - } + const symbol = resolved.members.get(name); + if (symbol && symbolIsValue(symbol)) { + return symbol; } } } @@ -4446,15 +4437,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 SMap()); + return getOrUpdateMap(properties, name, () => createUnionOrIntersectionProperty(type, name)); } /** @@ -4469,10 +4453,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 +4553,11 @@ namespace ts { } function symbolsToArray(symbols: SymbolTable): Symbol[] { - const result: Symbol[] = []; - for (const id in symbols) { + return mapAndFilterMap(symbols, (symbol, id) => { if (!isReservedMemberName(id)) { - result.push(symbols[id]); + return symbol; } - } - return result; + }); } function isJSDocOptionalParameter(node: ParameterDeclaration) { @@ -4866,7 +4848,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 +4959,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 +5005,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 getOrUpdateMap(links.instantiations, id, () => instantiateType(type, createTypeMapper(typeParameters, typeArguments))); } if (node.typeArguments) { error(node, Diagnostics.Type_0_is_not_generic, symbolToString(symbol)); @@ -5231,8 +5213,7 @@ namespace ts { } function createTupleType(elementTypes: Type[]) { - const id = getTypeListId(elementTypes); - return tupleTypes[id] || (tupleTypes[id] = createNewTupleType(elementTypes)); + return getOrUpdateMap(tupleTypes, getTypeListId(elementTypes), () => createNewTupleType(elementTypes)); } function createNewTupleType(elementTypes: Type[]) { @@ -5371,10 +5352,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 +5409,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 +5448,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 getOrUpdateMap(map, text, () => createLiteralType(flags, text)); } function getTypeFromLiteralTypeNode(node: LiteralTypeNode): Type { @@ -6154,7 +6135,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 +6153,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 +6183,7 @@ namespace ts { function checkTypeRelatedTo( source: Type, target: Type, - relation: Map, + relation: Relation, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: DiagnosticMessageChain): boolean { @@ -6210,7 +6191,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 +6532,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 +6546,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 +6563,7 @@ namespace ts { } sourceStack[depth] = source; targetStack[depth] = target; - maybeStack[depth] = {}; - maybeStack[depth][id] = RelationComparisonResult.Succeeded; + maybeStack[depth] = singletonMap(id, RelationComparisonResult.Succeeded); depth++; const saveExpandingFlags = expandingFlags; if (!(expandingFlags & 1) && isDeeplyNestedGeneric(source, sourceStack, depth)) expandingFlags |= 1; @@ -6618,7 +6598,7 @@ namespace ts { 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; } @@ -7223,11 +7203,11 @@ namespace ts { } function transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) { - const members: SymbolTable = {}; + const members: SymbolTable = new SMap(); 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 +7427,7 @@ namespace ts { let targetStack: Type[]; let depth = 0; let inferiority = 0; - const visited: Map = {}; + const visited = new SSet(); inferFromTypes(source, target); function isInProcess(source: Type, target: Type) { @@ -7581,10 +7561,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 +8315,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 SMap()); 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 +8346,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 +8366,7 @@ namespace ts { break; } } - return cache[key] = getUnionType(antecedentTypes, subtypeReduction); + return setAndReturn(cache, key, getUnionType(antecedentTypes, subtypeReduction)); } function isMatchingReferenceDiscriminant(expr: Expression) { @@ -8500,14 +8482,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 +9912,7 @@ namespace ts { // Grammar checking checkGrammarObjectLiteralExpression(node, inDestructuringPattern); - const propertiesTable: SymbolTable = {}; + const propertiesTable: SymbolTable = new SMap(); const propertiesArray: Symbol[] = []; const contextualType = getApparentTypeOfContextualType(node); const contextualTypeHasPattern = contextualType && contextualType.pattern && @@ -10012,7 +9994,7 @@ namespace ts { } } else { - propertiesTable[member.name] = member; + propertiesTable.set(member.name, member); } propertiesArray.push(member); } @@ -10021,12 +10003,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 +10081,7 @@ namespace ts { } } - function checkJsxAttribute(node: JsxAttribute, elementAttributesType: Type, nameTable: Map) { + function checkJsxAttribute(node: JsxAttribute, elementAttributesType: Type, nameTable: SSet) { let correspondingPropType: Type = undefined; // Look up the corresponding property for this attribute @@ -10139,34 +10121,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: SSet) { 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 getOrUpdateMap(jsxTypes, name, () => + getExportedTypeFromNamespace(JsxNames.JSX, name) || unknownType); } /** @@ -10471,7 +10451,8 @@ namespace ts { const targetAttributesType = getJsxElementAttributesType(node); - const nameTable: Map = {}; + const nameTable = new SSet(); + // 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 +10475,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 +11188,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 +11722,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 +13758,8 @@ namespace ts { function checkClassForDuplicateDeclarations(node: ClassLikeDeclaration) { const getter = 1, setter = 2, property = getter | setter; - const instanceNames: Map = {}; - const staticNames: Map = {}; + const instanceNames = new SMap(); + const staticNames = new SMap(); for (const member of node.members) { if (member.kind === SyntaxKind.Constructor) { for (const param of (member as ConstructorDeclaration).parameters) { @@ -13812,24 +13791,24 @@ namespace ts { } } - function addName(names: Map, location: Node, name: string, meaning: number) { - if (hasProperty(names, name)) { - const prev = names[name]; + function addName(names: SMap, 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 SSet(); for (const member of node.members) { if (member.kind == SyntaxKind.PropertySignature) { let memberName: string; @@ -13843,12 +13822,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 +15033,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 +15102,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 +16115,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 +16536,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 SMap<{ 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 +17285,14 @@ namespace ts { } function hasExportedMembers(moduleSymbol: Symbol) { - for (const id in moduleSymbol.exports) { - if (id !== "export=") { - return true; - } - } - return false; + return someInMap(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 +17301,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 +17323,7 @@ namespace ts { } } } - } + }); links.exportsChecked = true; } @@ -17632,7 +17599,7 @@ namespace ts { } function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] { - const symbols: SymbolTable = {}; + const symbols: SymbolTable = new SMap(); let memberFlags: NodeFlags = 0; if (isInsideWithStatementBody(location)) { @@ -17706,22 +17673,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 +18091,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 +18147,7 @@ namespace ts { // otherwise - check if at least one export is value symbolLinks.exportsSomeValue = hasExportAssignment ? !!(moduleSymbol.flags & SymbolFlags.Value) - : forEachValue(getExportsOfModule(moduleSymbol), isValue); + : someInMap(getExportsOfModule(moduleSymbol), isValue); } return symbolLinks.exportsSomeValue; @@ -18479,7 +18438,7 @@ namespace ts { } function hasGlobalName(name: string): boolean { - return hasProperty(globals, name); + return globals.has(name); } function getReferencedValueSymbol(reference: Identifier): Symbol { @@ -18502,17 +18461,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; - } - const resolvedDirective = resolvedTypeReferenceDirectives[key]; - if (!resolvedDirective) { - continue; + resolvedTypeReferenceDirectives.forEach((resolvedDirective, key) => { + if (resolvedDirective) { + const file = host.getSourceFile(resolvedDirective.resolvedFileName); + fileToDirective.set(file.path, key); } - const file = host.getSourceFile(resolvedDirective.resolvedFileName); - fileToDirective.set(file.path, key); - } + }); } return { getReferencedExportContainer, @@ -19294,7 +19248,7 @@ namespace ts { } function checkGrammarObjectLiteralExpression(node: ObjectLiteralExpression, inDestructuring: boolean) { - const seen: Map = {}; + const seen = new SMap(); const Property = 1; const GetAccessor = 2; const SetAccessor = 4; @@ -19356,17 +19310,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 +19334,7 @@ namespace ts { } function checkGrammarJsxElement(node: JsxOpeningLikeElement) { - const seen: Map = {}; + const seen = new SSet(); for (const attr of node.attributes) { if (attr.kind === SyntaxKind.JsxSpreadAttribute) { continue; @@ -19388,8 +19342,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..0c816a946de6f 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 SMap([ + ["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 SMap([ + ["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 SMap([ + ["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 SMap([ + ["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 SMap([ + ["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 SMap([ // 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: SMap; + shortOptionNames: SMap; } let optionNameMapCache: OptionNameMap; @@ -470,12 +470,12 @@ namespace ts { return optionNameMapCache; } - const optionNameMap: Map = {}; - const shortOptionNames: Map = {}; + const optionNameMap = new SMap(); + const shortOptionNames = new SMap(); 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 => { + forEachKeyInMap(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 = createMapFromValues(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 SMap(); // 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 SMap(); 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): ObjMap { // We watch a directory recursively if it contains a wildcard anywhere in a directory segment // of the pattern: // @@ -1063,7 +1064,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: ObjMap = {}; if (include !== undefined) { const recursiveKeys: string[] = []; for (const file of include) { @@ -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: SMap, wildcardFiles: SMap, 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: SMap, 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..4db422cfa2eb2 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 SMap(); 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 () => { @@ -451,7 +99,7 @@ namespace ts { return text.replace(/{(\d+)}/g, (match, index?) => args[+index + baseIndex]); } - export let localizedDiagnosticMessages: Map = undefined; + export let localizedDiagnosticMessages: ObjMap = undefined; export function getLocaleSpecificMessage(message: DiagnosticMessage) { return localizedDiagnosticMessages && localizedDiagnosticMessages[message.key] diff --git a/src/compiler/dataStructures.ts b/src/compiler/dataStructures.ts new file mode 100644 index 0000000000000..59c57b0cf7250 --- /dev/null +++ b/src/compiler/dataStructures.ts @@ -0,0 +1,768 @@ +// Map +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 SMap extends MapCommon { + /** + * Iterate over all entries in the map + * For halting iteration, see `findInMap`. + */ + forEach(fn: (value: V, key: string) => void): void; + } + export interface SMapConstructor { + new(): SMap; + /** Map whose entries are the given [key, value] pairs. */ + new(entries?: [string, V][]): SMap; + /** Clone another map. */ + new(otherMap: SMap): SMap; + } + + /** Number-keyed Map. */ + export interface NMap extends MapCommon {} + export interface NMapConstructor { + new(): NMap; + new(entries?: [number, V][]): NMap; + } + + abstract class ShimMapCommon implements MapCommon { + protected data: any; // ObjMap | 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 ShimSMap extends ShimMapCommon implements SMap { + data: ObjMap; + + constructor(entries?: [string, V][]); + constructor(otherMap: SMap); + constructor(argument?: any) { + super(); + this.data = {}; + + if (argument !== undefined) { + if (argument instanceof ShimSMap) { + 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 ShimNMap extends ShimMapCommon implements NMap { + data: V[]; + + constructor(entries?: [number, V][]) { + super(); + this.data = []; + if (entries) { + for (const [key, value] of entries) { + this.set(key, value); + } + } + } + + clear() { + this.data = []; + } + } + + declare const Map: (SMapConstructor & NMapConstructor) | undefined; + export const SMap: SMapConstructor = Map ? Map : ShimSMap; + export const NMap: NMapConstructor = Map ? Map : ShimNMap; + + /** Number of (key, value) pairs in a map. */ + export function mapSize(map: SMap): number { + if (map instanceof Map) { + // For native maps, this is available as a property. + return (map).size; + } + else { + let size = 0; + map.forEach(() => { size++; }); + return size; + } + } + + /** Iterate through a map, returning the first truthy value returned by `fn`, or undefined. */ + export function findInMap(map: SMap, fn: (value: V, key: string) => U | undefined): U | undefined { + if (map instanceof Map) { + // 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; + } + } + } + else { + return (>map).find(fn); + } + } + + /** Iterate over every key. */ + export function forEachKeyInMap(map: SMap, callback: (key: string) => void): void { + map.forEach((_, key) => callback(key)); + } + + /** Copy all entries from `source` to `target`, overwriting any already existing. */ + export function copyMap(source: SMap, target: SMap): void { + source.forEach((value, key) => { + target.set(key, value); + }); + } + + /** Array of all keys in a map. */ + export function keysArray(map: SMap): string[] { + const keys: string[] = []; + forEachKeyInMap(map, key => keys.push(key)); + return keys; + } + + /** Array of all values in a map. */ + export function valuesArray(map: SMap): 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 getOrUpdateMap(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 iff the predicate is true for some entry in the map. */ + export function someInMap(map: SMap, predicate: (value: V, key: string) => boolean): boolean { + return !!findInMap(map, predicate); + } + + /** True iff the predicate is true for every entry in the map. */ + export function allInMap(map: SMap, predicate: (value: V, key: string) => boolean): boolean { + return !findInMap(map, (value, key) => !predicate(value, key)); + } + + /** Array of every *defined* result of `mapAndFilter(value, key)`, called on each entry in the map. */ + export function mapAndFilterMap(map: SMap, 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 createMapFromArray(inputs: A[], getKey: (element: A) => string, getValue: (element: A) => V): SMap { + const result = new SMap(); + 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 createMapFromKeys(keys: string[], getValue: (key: string) => V): SMap { + return createMapFromArray(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 createMapFromValues(values: V[], getKey: (value: V) => string): SMap { + return createMapFromArray(values, getKey, value => value); + } + + export function mapKeys(map: SMap, getNewKey: (key: string) => string): SMap { + const result = new SMap(); + 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: SMap, getNewValue: (value: V) => V): void { + map.forEach((value, key) => { + map.set(key, getNewValue(value)); + }); + } + + /** Map of a single entry. */ + export function singletonMap(key: string, value: V): SMap { + return new SMap([[key, value]]); + } +} + +// Set +namespace ts { + export interface SSet { + /** 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 SSetConstructor { + new(values?: string[]): SSet; + } + + class ShimSSet implements SSet { + data: ObjMap; + + 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: SSetConstructor | undefined; + export const SSet: SSetConstructor = Set ? Set : ShimSSet; + + /** False iff there are any values in the set. */ + export function setIsEmpty(set: SSet): boolean { + if (set instanceof Set) { + return !(set).size; + } + else { + (set).isEmpty(); + } + } + + /** Add every value in `source` to `target`. */ + export function copySet(source: SSet, target: SSet): void { + source.forEach(element => target.add(element)); + } + + /** If `shouldBeInSet`, put `value` into the set; otherwise, remove it. */ + export function addOrDelete(set: SSet, value: string, shouldBeInSet: boolean): void { + if (shouldBeInSet) { + set.add(value); + } + else { + set.delete(value); + } + } +} + +// ObjMap +namespace ts { + export interface ObjMap { + [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: ObjMap, 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 an ObjMap to an SMap. */ + export function smapOfObjMap(objMap: ObjMap): SMap { + const result = new SMap(); + for (const key in objMap) { + if (hasProperty(objMap, key)) { + result.set(key, objMap[key]); + } + } + return result; + } + + export function forEachValueAndKey(map: ObjMap, callback: (value: T, key: string) => void): void { + for (const id in map) { + if (hasProperty(map, id)) { + callback(map[id], id); + } + } + } + + export function forEachKey(map: ObjMap, callback: (key: string) => U): U { + let result: U; + for (const id in map) { + if (result = callback(id)) break; + } + return result; + } + + export function forEachValue(map: ObjMap, 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: ObjMap, key: string): boolean { + return hasOwnProperty.call(map, key); + } + + export function getKeys(map: ObjMap): string[] { + const keys: string[] = []; + for (const key in map) { + keys.push(key); + } + return keys; + } + + export function getProperty(map: ObjMap, 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: ObjMap) { + 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 ObjMap<{}>>(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 objMapIsEqualTo(map1: ObjMap, map2: ObjMap): boolean { + if (!map1 || !map2) { + return map1 === map2; + } + return containsAll(map1, map2) && containsAll(map2, map1); + } + + function containsAll(map: ObjMap, other: ObjMap): 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 copyObjMap(source: ObjMap, target: ObjMap): 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..0372ed11c8d51 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: SMap; let isCurrentFileExternalModule: boolean; let reportedDeclarationError = false; let errorNameNode: DeclarationName; @@ -75,7 +75,7 @@ namespace ts { // and we could be collecting these paths from multiple files into single one with --out option let referencesOutput = ""; - let usedTypeDirectiveReferences: Map; + let usedTypeDirectiveReferences: ObjMap; // Emit references corresponding to each file const emittedReferencedFiles: SourceFile[] = []; @@ -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..8e9d1bea81409 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: ObjMap = { "quot": 0x0022, "amp": 0x0026, "apos": 0x0027, @@ -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; } } @@ -422,15 +423,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge * set of labels that occurred inside the converted loop * used to determine if labeled jump can be emitted as is or it should be dispatched to calling code */ - labels?: Map; + labels?: ObjMap; /* * collection of labeled jumps that transfer control outside the converted loop. * maps store association 'label -> labelMarker' where * - label - value of label as it appear in code * - label marker - return value that should be interpreted by calling code as 'jump to