@@ -464,7 +464,19 @@ namespace ts {
464
464
const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true);
465
465
466
466
const globals = createSymbolTable();
467
- let amalgamatedDuplicates: Map<{ firstFile: SourceFile, secondFile: SourceFile, firstFileInstances: Map<{ instances: Node[], blockScoped: boolean }>, secondFileInstances: Map<{ instances: Node[], blockScoped: boolean }> }> | undefined;
467
+ interface DuplicateInfoForSymbol {
468
+ readonly firstFileLocations: Node[];
469
+ readonly secondFileLocations: Node[];
470
+ readonly isBlockScoped: boolean;
471
+ }
472
+ interface DuplicateInfoForFiles {
473
+ readonly firstFile: SourceFile;
474
+ readonly secondFile: SourceFile;
475
+ /** Key is symbol name. */
476
+ readonly conflictingSymbols: Map<DuplicateInfoForSymbol>;
477
+ }
478
+ /** Key is "/path/to/a.ts|/path/to/b.ts". */
479
+ let amalgamatedDuplicates: Map<DuplicateInfoForFiles> | undefined;
468
480
const reverseMappedCache = createMap<Type | undefined>();
469
481
let ambientModulesCache: Symbol[] | undefined;
470
482
/**
@@ -883,51 +895,43 @@ namespace ts {
883
895
: Diagnostics.Duplicate_identifier_0;
884
896
const sourceSymbolFile = source.declarations && getSourceFileOfNode(source.declarations[0]);
885
897
const targetSymbolFile = target.declarations && getSourceFileOfNode(target.declarations[0]);
898
+ const symbolName = symbolToString(source);
886
899
887
900
// Collect top-level duplicate identifier errors into one mapping, so we can then merge their diagnostics if there are a bunch
888
901
if (sourceSymbolFile && targetSymbolFile && amalgamatedDuplicates && !isEitherEnum && sourceSymbolFile !== targetSymbolFile) {
889
902
const firstFile = comparePaths(sourceSymbolFile.path, targetSymbolFile.path) === Comparison.LessThan ? sourceSymbolFile : targetSymbolFile;
890
903
const secondFile = firstFile === sourceSymbolFile ? targetSymbolFile : sourceSymbolFile;
891
- const cacheKey = `${firstFile.path}|${secondFile.path}`;
892
- const existing = amalgamatedDuplicates.get(cacheKey) || { firstFile, secondFile, firstFileInstances: createMap(), secondFileInstances: createMap() };
893
- const symbolName = symbolToString(source);
894
- const firstInstanceList = existing.firstFileInstances.get(symbolName) || { instances: [], blockScoped: isEitherBlockScoped };
895
- const secondInstanceList = existing.secondFileInstances.get(symbolName) || { instances: [], blockScoped: isEitherBlockScoped };
896
-
897
- forEach(source.declarations, node => {
898
- const errorNode = (getExpandoInitializer(node, /*isPrototypeAssignment*/ false) ? getNameOfExpando(node) : getNameOfDeclaration(node)) || node;
899
- const targetList = sourceSymbolFile === firstFile ? firstInstanceList : secondInstanceList;
900
- targetList.instances.push(errorNode);
901
- });
902
- forEach(target.declarations, node => {
903
- const errorNode = (getExpandoInitializer(node, /*isPrototypeAssignment*/ false) ? getNameOfExpando(node) : getNameOfDeclaration(node)) || node;
904
- const targetList = targetSymbolFile === firstFile ? firstInstanceList : secondInstanceList;
905
- targetList.instances.push(errorNode);
906
- });
907
-
908
- existing.firstFileInstances.set(symbolName, firstInstanceList);
909
- existing.secondFileInstances.set(symbolName, secondInstanceList);
910
- amalgamatedDuplicates.set(cacheKey, existing);
911
- return target;
904
+ const filesDuplicates = getOrUpdate<DuplicateInfoForFiles>(amalgamatedDuplicates, `${firstFile.path}|${secondFile.path}`, () =>
905
+ ({ firstFile, secondFile, conflictingSymbols: createMap() }));
906
+ const conflictingSymbolInfo = getOrUpdate<DuplicateInfoForSymbol>(filesDuplicates.conflictingSymbols, symbolName, () =>
907
+ ({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] }));
908
+ for (const { locs, symbol } of [{ locs: conflictingSymbolInfo.firstFileLocations, symbol: source }, { locs: conflictingSymbolInfo.secondFileLocations, symbol: target }]) {
909
+ for (const decl of symbol.declarations) {
910
+ pushIfUnique(locs, (getExpandoInitializer(decl, /*isPrototypeAssignment*/ false) ? getNameOfExpando(decl) : getNameOfDeclaration(decl)) || decl);
911
+ }
912
+ }
913
+ }
914
+ else {
915
+ addDuplicateDeclarationErrorsForSymbols(source, message, symbolName, target);
916
+ addDuplicateDeclarationErrorsForSymbols(target, message, symbolName, source);
912
917
}
913
- const symbolName = symbolToString(source);
914
- addDuplicateDeclarationErrorsForSymbols(source, message, symbolName, target);
915
- addDuplicateDeclarationErrorsForSymbols(target, message, symbolName, source);
916
918
}
917
919
return target;
918
920
}
919
921
920
922
function addDuplicateDeclarationErrorsForSymbols(target: Symbol, message: DiagnosticMessage, symbolName: string, source: Symbol) {
921
923
forEach(target.declarations, node => {
922
924
const errorNode = (getExpandoInitializer(node, /*isPrototypeAssignment*/ false) ? getNameOfExpando(node) : getNameOfDeclaration(node)) || node;
923
- addDuplicateDeclarationError(errorNode, message, symbolName, source.declarations && source.declarations[0] );
925
+ addDuplicateDeclarationError(errorNode, message, symbolName, source.declarations);
924
926
});
925
927
}
926
928
927
- function addDuplicateDeclarationError(errorNode: Node, message: DiagnosticMessage, symbolName: string, relatedNode: Node | undefined) {
929
+ function addDuplicateDeclarationError(errorNode: Node, message: DiagnosticMessage, symbolName: string, relatedNodes: ReadonlyArray< Node> | undefined) {
928
930
const err = lookupOrIssueError(errorNode, message, symbolName);
929
- if (relatedNode && length(err.relatedInformation) < 5) {
930
- addRelatedInfo(err, !length(err.relatedInformation) ? createDiagnosticForNode(relatedNode, Diagnostics._0_was_also_declared_here, symbolName) : createDiagnosticForNode(relatedNode, Diagnostics.and_here));
931
+ for (const relatedNode of relatedNodes || emptyArray) {
932
+ err.relatedInformation = err.relatedInformation || [];
933
+ if (length(err.relatedInformation) >= 5) continue;
934
+ addRelatedInfo(err, !length(err.relatedInformation) ? createDiagnosticForNode(relatedNode, Diagnostics._0_was_also_declared_here, symbolName) : createDiagnosticForNode(relatedNode, Diagnostics.and_here))
931
935
}
932
936
}
933
937
@@ -28842,38 +28846,32 @@ namespace ts {
28842
28846
}
28843
28847
}
28844
28848
28845
- amalgamatedDuplicates.forEach(({ firstFile, secondFile, firstFileInstances, secondFileInstances }) => {
28846
- const conflictingKeys = arrayFrom(firstFileInstances.keys());
28849
+ amalgamatedDuplicates.forEach(({ firstFile, secondFile, conflictingSymbols }) => {
28847
28850
// If not many things conflict, issue individual errors
28848
- if (conflictingKeys.length < 8) {
28849
- addErrorsForDuplicates(firstFileInstances, secondFileInstances);
28850
- addErrorsForDuplicates(secondFileInstances, firstFileInstances);
28851
- return;
28851
+ if (conflictingSymbols.size < 8) {
28852
+ conflictingSymbols.forEach(({ isBlockScoped, firstFileLocations, secondFileLocations }, symbolName) => {
28853
+ const message = isBlockScoped ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 : Diagnostics.Duplicate_identifier_0;
28854
+ for (const { firstLocs, secondLocs } of [{ firstLocs: firstFileLocations, secondLocs: secondFileLocations }, { firstLocs: secondFileLocations, secondLocs: firstFileLocations }]) {
28855
+ for (const node of firstLocs) {
28856
+ addDuplicateDeclarationError(node, message, symbolName, secondLocs);
28857
+ }
28858
+ }
28859
+ });
28860
+ }
28861
+ else {
28862
+ // Otheriwse issue top-level error since the files appear very identical in terms of what they appear
28863
+ const list = arrayFrom(conflictingSymbols.keys()).join(", ");
28864
+ diagnostics.add(addRelatedInfo(
28865
+ createDiagnosticForNode(firstFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list),
28866
+ createDiagnosticForNode(secondFile, Diagnostics.Conflicts_are_in_this_file)
28867
+ ));
28868
+ diagnostics.add(addRelatedInfo(
28869
+ createDiagnosticForNode(secondFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list),
28870
+ createDiagnosticForNode(firstFile, Diagnostics.Conflicts_are_in_this_file)
28871
+ ));
28852
28872
}
28853
- // Otheriwse issue top-level error since the files appear very identical in terms of what they appear
28854
- const list = conflictingKeys.join(", ");
28855
- diagnostics.add(addRelatedInfo(
28856
- createDiagnosticForNode(firstFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list),
28857
- createDiagnosticForNode(secondFile, Diagnostics.Conflicts_are_in_this_file)
28858
- ));
28859
- diagnostics.add(addRelatedInfo(
28860
- createDiagnosticForNode(secondFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list),
28861
- createDiagnosticForNode(firstFile, Diagnostics.Conflicts_are_in_this_file)
28862
- ));
28863
28873
});
28864
28874
amalgamatedDuplicates = undefined;
28865
-
28866
- function addErrorsForDuplicates(secondFileInstances: Map<{ instances: Node[]; blockScoped: boolean; }>, firstFileInstances: Map<{ instances: Node[]; blockScoped: boolean; }>) {
28867
- secondFileInstances.forEach((locations, symbolName) => {
28868
- const firstFileEquivalent = firstFileInstances.get(symbolName)!;
28869
- const message = locations.blockScoped
28870
- ? Diagnostics.Cannot_redeclare_block_scoped_variable_0
28871
- : Diagnostics.Duplicate_identifier_0;
28872
- locations.instances.forEach(node => {
28873
- addDuplicateDeclarationError(node, message, symbolName, firstFileEquivalent.instances[0]);
28874
- });
28875
- });
28876
- }
28877
28875
}
28878
28876
28879
28877
function checkExternalEmitHelpers(location: Node, helpers: ExternalEmitHelpers) {
0 commit comments