@@ -422,6 +422,7 @@ namespace ts {
422422 const jsObjectLiteralIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false);
423423
424424 const globals = createSymbolTable();
425+ let amalgamatedDuplicates: Map<{ firstFile: SourceFile, secondFile: SourceFile, firstFileInstances: Map<{ instances: Node[], blockScoped: boolean }>, secondFileInstances: Map<{ instances: Node[], blockScoped: boolean }> }> | undefined;
425426 const reverseMappedCache = createMap<Type | undefined>();
426427 let ambientModulesCache: Symbol[] | undefined;
427428 /**
@@ -693,6 +694,28 @@ namespace ts {
693694 return emitResolver;
694695 }
695696
697+ function lookupOrIssueError(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
698+ const diagnostic = location
699+ ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3)
700+ : createCompilerDiagnostic(message, arg0, arg1, arg2, arg3);
701+ const existing = diagnostics.lookup(diagnostic);
702+ if (existing) {
703+ return existing;
704+ }
705+ else {
706+ diagnostics.add(diagnostic);
707+ return diagnostic;
708+ }
709+ }
710+
711+ function addRelatedInfo(diagnostic: Diagnostic, ...relatedInformation: DiagnosticRelatedInformation[]) {
712+ if (!diagnostic.relatedInformation) {
713+ diagnostic.relatedInformation = [];
714+ }
715+ diagnostic.relatedInformation.push(...relatedInformation);
716+ return diagnostic;
717+ }
718+
696719 function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
697720 const diagnostic = location
698721 ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3)
@@ -803,23 +826,63 @@ namespace ts {
803826 error(getNameOfDeclaration(source.declarations[0]), Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, symbolToString(target));
804827 }
805828 else {
806- const message = target.flags & SymbolFlags.Enum || source.flags & SymbolFlags.Enum
829+ const isEitherEnum = !!(target.flags & SymbolFlags.Enum || source.flags & SymbolFlags.Enum);
830+ const isEitherBlockScoped = !!(target.flags & SymbolFlags.BlockScopedVariable || source.flags & SymbolFlags.BlockScopedVariable);
831+ const message = isEitherEnum
807832 ? Diagnostics.Enum_declarations_can_only_merge_with_namespace_or_other_enum_declarations
808- : target.flags & SymbolFlags.BlockScopedVariable || source.flags & SymbolFlags.BlockScopedVariable
833+ : isEitherBlockScoped
809834 ? Diagnostics.Cannot_redeclare_block_scoped_variable_0
810835 : Diagnostics.Duplicate_identifier_0;
811- forEach(source.declarations, node => {
812- const errorNode = (getJavascriptInitializer(node, /*isPrototypeAssignment*/ false) ? getOuterNameOfJsInitializer(node) : getNameOfDeclaration(node)) || node;
813- error(errorNode, message, symbolToString(source));
814- });
815- forEach(target.declarations, node => {
816- const errorNode = (getJavascriptInitializer(node, /*isPrototypeAssignment*/ false) ? getOuterNameOfJsInitializer(node) : getNameOfDeclaration(node)) || node;
817- error(errorNode, message, symbolToString(source));
818- });
836+ const sourceSymbolFile = source.declarations && getSourceFileOfNode(source.declarations[0]);
837+ const targetSymbolFile = target.declarations && getSourceFileOfNode(target.declarations[0]);
838+
839+ // Collect top-level duplicate identifier errors into one mapping, so we can then merge their diagnostics if there are a bunch
840+ if (sourceSymbolFile && targetSymbolFile && amalgamatedDuplicates && !isEitherEnum && sourceSymbolFile !== targetSymbolFile) {
841+ const firstFile = comparePaths(sourceSymbolFile.path, targetSymbolFile.path) === Comparison.LessThan ? sourceSymbolFile : targetSymbolFile;
842+ const secondFile = firstFile === sourceSymbolFile ? targetSymbolFile : sourceSymbolFile;
843+ const cacheKey = `${firstFile.path}|${secondFile.path}`;
844+ const existing = amalgamatedDuplicates.get(cacheKey) || { firstFile, secondFile, firstFileInstances: createMap(), secondFileInstances: createMap() };
845+ const symbolName = symbolToString(source);
846+ const firstInstanceList = existing.firstFileInstances.get(symbolName) || { instances: [], blockScoped: isEitherBlockScoped };
847+ const secondInstanceList = existing.secondFileInstances.get(symbolName) || { instances: [], blockScoped: isEitherBlockScoped };
848+
849+ forEach(source.declarations, node => {
850+ const errorNode = (getJavascriptInitializer(node, /*isPrototypeAssignment*/ false) ? getOuterNameOfJsInitializer(node) : getNameOfDeclaration(node)) || node;
851+ const targetList = sourceSymbolFile === firstFile ? firstInstanceList : secondInstanceList;
852+ targetList.instances.push(errorNode);
853+ });
854+ forEach(target.declarations, node => {
855+ const errorNode = (getJavascriptInitializer(node, /*isPrototypeAssignment*/ false) ? getOuterNameOfJsInitializer(node) : getNameOfDeclaration(node)) || node;
856+ const targetList = targetSymbolFile === firstFile ? firstInstanceList : secondInstanceList;
857+ targetList.instances.push(errorNode);
858+ });
859+
860+ existing.firstFileInstances.set(symbolName, firstInstanceList);
861+ existing.secondFileInstances.set(symbolName, secondInstanceList);
862+ amalgamatedDuplicates.set(cacheKey, existing);
863+ return target;
864+ }
865+ const symbolName = symbolToString(source);
866+ addDuplicateDeclarationErrorsForSymbols(source, message, symbolName, target);
867+ addDuplicateDeclarationErrorsForSymbols(target, message, symbolName, source);
819868 }
820869 return target;
821870 }
822871
872+ function addDuplicateDeclarationErrorsForSymbols(target: Symbol, message: DiagnosticMessage, symbolName: string, source: Symbol) {
873+ forEach(target.declarations, node => {
874+ const errorNode = (getJavascriptInitializer(node, /*isPrototypeAssignment*/ false) ? getOuterNameOfJsInitializer(node) : getNameOfDeclaration(node)) || node;
875+ addDuplicateDeclarationError(errorNode, message, symbolName, source.declarations && source.declarations[0]);
876+ });
877+ }
878+
879+ function addDuplicateDeclarationError(errorNode: Node, message: DiagnosticMessage, symbolName: string, relatedNode: Node | undefined) {
880+ const err = lookupOrIssueError(errorNode, message, symbolName);
881+ if (relatedNode && length(err.relatedInformation) < 5) {
882+ addRelatedInfo(err, !length(err.relatedInformation) ? createDiagnosticForNode(relatedNode, Diagnostics._0_was_also_declared_here, symbolName) : createDiagnosticForNode(relatedNode, Diagnostics.and_here));
883+ }
884+ }
885+
823886 function combineSymbolTables(first: SymbolTable | undefined, second: SymbolTable | undefined): SymbolTable | undefined {
824887 if (!hasEntries(first)) return second;
825888 if (!hasEntries(second)) return first;
@@ -27449,6 +27512,8 @@ namespace ts {
2744927512 bindSourceFile(file, compilerOptions);
2745027513 }
2745127514
27515+ amalgamatedDuplicates = createMap();
27516+
2745227517 // Initialize global symbol table
2745327518 let augmentations: ReadonlyArray<StringLiteral | Identifier>[] | undefined;
2745427519 for (const file of host.getSourceFiles()) {
@@ -27526,6 +27591,39 @@ namespace ts {
2752627591 }
2752727592 }
2752827593 }
27594+
27595+ amalgamatedDuplicates.forEach(({ firstFile, secondFile, firstFileInstances, secondFileInstances }) => {
27596+ const conflictingKeys = arrayFrom(firstFileInstances.keys());
27597+ // If not many things conflict, issue individual errors
27598+ if (conflictingKeys.length < 8) {
27599+ addErrorsForDuplicates(firstFileInstances, secondFileInstances);
27600+ addErrorsForDuplicates(secondFileInstances, firstFileInstances);
27601+ return;
27602+ }
27603+ // Otheriwse issue top-level error since the files appear very identical in terms of what they appear
27604+ const list = conflictingKeys.join(", ");
27605+ diagnostics.add(addRelatedInfo(
27606+ createDiagnosticForNode(firstFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list),
27607+ createDiagnosticForNode(secondFile, Diagnostics.Conflicts_are_in_this_file)
27608+ ));
27609+ diagnostics.add(addRelatedInfo(
27610+ createDiagnosticForNode(secondFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list),
27611+ createDiagnosticForNode(firstFile, Diagnostics.Conflicts_are_in_this_file)
27612+ ));
27613+ });
27614+ amalgamatedDuplicates = undefined;
27615+
27616+ function addErrorsForDuplicates(secondFileInstances: Map<{ instances: Node[]; blockScoped: boolean; }>, firstFileInstances: Map<{ instances: Node[]; blockScoped: boolean; }>) {
27617+ secondFileInstances.forEach((locations, symbolName) => {
27618+ const firstFileEquivalent = firstFileInstances.get(symbolName)!;
27619+ const message = locations.blockScoped
27620+ ? Diagnostics.Cannot_redeclare_block_scoped_variable_0
27621+ : Diagnostics.Duplicate_identifier_0;
27622+ locations.instances.forEach(node => {
27623+ addDuplicateDeclarationError(node, message, symbolName, firstFileEquivalent.instances[0]);
27624+ });
27625+ });
27626+ }
2752927627 }
2753027628
2753127629 function checkExternalEmitHelpers(location: Node, helpers: ExternalEmitHelpers) {
0 commit comments