@@ -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;
@@ -1592,14 +1655,25 @@ namespace ts {
15921655 if (declaration === undefined) return Debug.fail("Declaration to checkResolvedBlockScopedVariable is undefined");
15931656
15941657 if (!(declaration.flags & NodeFlags.Ambient) && !isBlockScopedNameDeclaredBeforeUse(declaration, errorLocation)) {
1658+ let diagnosticMessage;
1659+ const declarationName = declarationNameToString(getNameOfDeclaration(declaration));
15951660 if (result.flags & SymbolFlags.BlockScopedVariable) {
1596- error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationNameToString(getNameOfDeclaration(declaration)) );
1661+ diagnosticMessage = error(errorLocation, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationName );
15971662 }
15981663 else if (result.flags & SymbolFlags.Class) {
1599- error(errorLocation, Diagnostics.Class_0_used_before_its_declaration, declarationNameToString(getNameOfDeclaration(declaration)) );
1664+ diagnosticMessage = error(errorLocation, Diagnostics.Class_0_used_before_its_declaration, declarationName );
16001665 }
16011666 else if (result.flags & SymbolFlags.RegularEnum) {
1602- error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, declarationNameToString(getNameOfDeclaration(declaration)));
1667+ diagnosticMessage = error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, declarationName);
1668+ }
1669+ else {
1670+ Debug.assert(!!(result.flags & SymbolFlags.ConstEnum));
1671+ }
1672+
1673+ if (diagnosticMessage) {
1674+ addRelatedInfo(diagnosticMessage,
1675+ createDiagnosticForNode(declaration, Diagnostics._0_was_declared_here, declarationName)
1676+ );
16031677 }
16041678 }
16051679 }
@@ -17397,16 +17471,24 @@ namespace ts {
1739717471 return;
1739817472 }
1739917473
17474+ let diagnosticMessage;
17475+ const declarationName = idText(right);
1740017476 if (isInPropertyInitializer(node) &&
1740117477 !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)
1740217478 && !isPropertyDeclaredInAncestorClass(prop)) {
17403- error(right, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, idText(right) );
17479+ diagnosticMessage = error(right, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, declarationName );
1740417480 }
1740517481 else if (valueDeclaration.kind === SyntaxKind.ClassDeclaration &&
1740617482 node.parent.kind !== SyntaxKind.TypeReference &&
1740717483 !(valueDeclaration.flags & NodeFlags.Ambient) &&
1740817484 !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)) {
17409- error(right, Diagnostics.Class_0_used_before_its_declaration, idText(right));
17485+ diagnosticMessage = error(right, Diagnostics.Class_0_used_before_its_declaration, declarationName);
17486+ }
17487+
17488+ if (diagnosticMessage) {
17489+ addRelatedInfo(diagnosticMessage,
17490+ createDiagnosticForNode(valueDeclaration, Diagnostics._0_was_declared_here, declarationName)
17491+ );
1741017492 }
1741117493 }
1741217494
@@ -19020,8 +19102,8 @@ namespace ts {
1902019102 if (importNode && !isImportCall(importNode)) {
1902119103 const sigs = getSignaturesOfType(getTypeOfSymbol(getSymbolLinks(apparentType.symbol).target!), kind);
1902219104 if (!sigs || !sigs.length) return;
19023- diagnostic.relatedInformation = diagnostic.relatedInformation || [] ;
19024- diagnostic.relatedInformation.push( createDiagnosticForNode(importNode, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead)) ;
19105+ Debug.assert(! diagnostic.relatedInformation) ;
19106+ diagnostic.relatedInformation = [ createDiagnosticForNode(importNode, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead)] ;
1902519107 }
1902619108 }
1902719109
@@ -27449,6 +27531,8 @@ namespace ts {
2744927531 bindSourceFile(file, compilerOptions);
2745027532 }
2745127533
27534+ amalgamatedDuplicates = createMap();
27535+
2745227536 // Initialize global symbol table
2745327537 let augmentations: ReadonlyArray<StringLiteral | Identifier>[] | undefined;
2745427538 for (const file of host.getSourceFiles()) {
@@ -27526,6 +27610,39 @@ namespace ts {
2752627610 }
2752727611 }
2752827612 }
27613+
27614+ amalgamatedDuplicates.forEach(({ firstFile, secondFile, firstFileInstances, secondFileInstances }) => {
27615+ const conflictingKeys = arrayFrom(firstFileInstances.keys());
27616+ // If not many things conflict, issue individual errors
27617+ if (conflictingKeys.length < 8) {
27618+ addErrorsForDuplicates(firstFileInstances, secondFileInstances);
27619+ addErrorsForDuplicates(secondFileInstances, firstFileInstances);
27620+ return;
27621+ }
27622+ // Otheriwse issue top-level error since the files appear very identical in terms of what they appear
27623+ const list = conflictingKeys.join(", ");
27624+ diagnostics.add(addRelatedInfo(
27625+ createDiagnosticForNode(firstFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list),
27626+ createDiagnosticForNode(secondFile, Diagnostics.Conflicts_are_in_this_file)
27627+ ));
27628+ diagnostics.add(addRelatedInfo(
27629+ createDiagnosticForNode(secondFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list),
27630+ createDiagnosticForNode(firstFile, Diagnostics.Conflicts_are_in_this_file)
27631+ ));
27632+ });
27633+ amalgamatedDuplicates = undefined;
27634+
27635+ function addErrorsForDuplicates(secondFileInstances: Map<{ instances: Node[]; blockScoped: boolean; }>, firstFileInstances: Map<{ instances: Node[]; blockScoped: boolean; }>) {
27636+ secondFileInstances.forEach((locations, symbolName) => {
27637+ const firstFileEquivalent = firstFileInstances.get(symbolName)!;
27638+ const message = locations.blockScoped
27639+ ? Diagnostics.Cannot_redeclare_block_scoped_variable_0
27640+ : Diagnostics.Duplicate_identifier_0;
27641+ locations.instances.forEach(node => {
27642+ addDuplicateDeclarationError(node, message, symbolName, firstFileEquivalent.instances[0]);
27643+ });
27644+ });
27645+ }
2752927646 }
2753027647
2753127648 function checkExternalEmitHelpers(location: Node, helpers: ExternalEmitHelpers) {
0 commit comments