@@ -422,6 +422,7 @@ namespace ts {
422
422
const jsObjectLiteralIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false);
423
423
424
424
const globals = createSymbolTable();
425
+ let amalgamatedDuplicates: Map<{ firstFile: SourceFile, secondFile: SourceFile, firstFileInstances: Map<{ instances: Node[], blockScoped: boolean }>, secondFileInstances: Map<{ instances: Node[], blockScoped: boolean }> }> | undefined;
425
426
const reverseMappedCache = createMap<Type | undefined>();
426
427
let ambientModulesCache: Symbol[] | undefined;
427
428
/**
@@ -693,6 +694,28 @@ namespace ts {
693
694
return emitResolver;
694
695
}
695
696
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
+
696
719
function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
697
720
const diagnostic = location
698
721
? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3)
@@ -803,23 +826,63 @@ namespace ts {
803
826
error(getNameOfDeclaration(source.declarations[0]), Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, symbolToString(target));
804
827
}
805
828
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
807
832
? Diagnostics.Enum_declarations_can_only_merge_with_namespace_or_other_enum_declarations
808
- : target.flags & SymbolFlags.BlockScopedVariable || source.flags & SymbolFlags.BlockScopedVariable
833
+ : isEitherBlockScoped
809
834
? Diagnostics.Cannot_redeclare_block_scoped_variable_0
810
835
: 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);
819
868
}
820
869
return target;
821
870
}
822
871
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
+
823
886
function combineSymbolTables(first: SymbolTable | undefined, second: SymbolTable | undefined): SymbolTable | undefined {
824
887
if (!hasEntries(first)) return second;
825
888
if (!hasEntries(second)) return first;
@@ -27449,6 +27512,8 @@ namespace ts {
27449
27512
bindSourceFile(file, compilerOptions);
27450
27513
}
27451
27514
27515
+ amalgamatedDuplicates = createMap();
27516
+
27452
27517
// Initialize global symbol table
27453
27518
let augmentations: ReadonlyArray<StringLiteral | Identifier>[] | undefined;
27454
27519
for (const file of host.getSourceFiles()) {
@@ -27526,6 +27591,39 @@ namespace ts {
27526
27591
}
27527
27592
}
27528
27593
}
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
+ }
27529
27627
}
27530
27628
27531
27629
function checkExternalEmitHelpers(location: Node, helpers: ExternalEmitHelpers) {
0 commit comments