Clean up amalgamatedDuplicates (#27285)

* Clean up amalgamatedDuplicates

* Code review
This commit is contained in:
Andy
2018-10-01 12:16:49 -07:00
committed by GitHub
parent 115636bb93
commit 8feddcd16d
7 changed files with 181 additions and 56 deletions

View File

@@ -465,7 +465,19 @@ namespace ts {
const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true);
const globals = createSymbolTable();
let amalgamatedDuplicates: Map<{ firstFile: SourceFile, secondFile: SourceFile, firstFileInstances: Map<{ instances: Node[], blockScoped: boolean }>, secondFileInstances: Map<{ instances: Node[], blockScoped: boolean }> }> | undefined;
interface DuplicateInfoForSymbol {
readonly firstFileLocations: Node[];
readonly secondFileLocations: Node[];
readonly isBlockScoped: boolean;
}
interface DuplicateInfoForFiles {
readonly firstFile: SourceFile;
readonly secondFile: SourceFile;
/** Key is symbol name. */
readonly conflictingSymbols: Map<DuplicateInfoForSymbol>;
}
/** Key is "/path/to/a.ts|/path/to/b.ts". */
let amalgamatedDuplicates: Map<DuplicateInfoForFiles> | undefined;
const reverseMappedCache = createMap<Type | undefined>();
let ambientModulesCache: Symbol[] | undefined;
/**
@@ -886,50 +898,45 @@ namespace ts {
: Diagnostics.Duplicate_identifier_0;
const sourceSymbolFile = source.declarations && getSourceFileOfNode(source.declarations[0]);
const targetSymbolFile = target.declarations && getSourceFileOfNode(target.declarations[0]);
const symbolName = symbolToString(source);
// Collect top-level duplicate identifier errors into one mapping, so we can then merge their diagnostics if there are a bunch
if (sourceSymbolFile && targetSymbolFile && amalgamatedDuplicates && !isEitherEnum && sourceSymbolFile !== targetSymbolFile) {
const firstFile = comparePaths(sourceSymbolFile.path, targetSymbolFile.path) === Comparison.LessThan ? sourceSymbolFile : targetSymbolFile;
const secondFile = firstFile === sourceSymbolFile ? targetSymbolFile : sourceSymbolFile;
const cacheKey = `${firstFile.path}|${secondFile.path}`;
const existing = amalgamatedDuplicates.get(cacheKey) || { firstFile, secondFile, firstFileInstances: createMap(), secondFileInstances: createMap() };
const symbolName = symbolToString(source);
const firstInstanceList = existing.firstFileInstances.get(symbolName) || { instances: [], blockScoped: isEitherBlockScoped };
const secondInstanceList = existing.secondFileInstances.get(symbolName) || { instances: [], blockScoped: isEitherBlockScoped };
forEach(source.declarations, node => {
const errorNode = (getExpandoInitializer(node, /*isPrototypeAssignment*/ false) ? getNameOfExpando(node) : getNameOfDeclaration(node)) || node;
const targetList = sourceSymbolFile === firstFile ? firstInstanceList : secondInstanceList;
targetList.instances.push(errorNode);
});
forEach(target.declarations, node => {
const errorNode = (getExpandoInitializer(node, /*isPrototypeAssignment*/ false) ? getNameOfExpando(node) : getNameOfDeclaration(node)) || node;
const targetList = targetSymbolFile === firstFile ? firstInstanceList : secondInstanceList;
targetList.instances.push(errorNode);
});
existing.firstFileInstances.set(symbolName, firstInstanceList);
existing.secondFileInstances.set(symbolName, secondInstanceList);
amalgamatedDuplicates.set(cacheKey, existing);
return target;
const filesDuplicates = getOrUpdate<DuplicateInfoForFiles>(amalgamatedDuplicates, `${firstFile.path}|${secondFile.path}`, () =>
({ firstFile, secondFile, conflictingSymbols: createMap() }));
const conflictingSymbolInfo = getOrUpdate<DuplicateInfoForSymbol>(filesDuplicates.conflictingSymbols, symbolName, () =>
({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] }));
addDuplicateLocations(conflictingSymbolInfo.firstFileLocations, source);
addDuplicateLocations(conflictingSymbolInfo.secondFileLocations, target);
}
else {
addDuplicateDeclarationErrorsForSymbols(source, message, symbolName, target);
addDuplicateDeclarationErrorsForSymbols(target, message, symbolName, source);
}
const symbolName = symbolToString(source);
addDuplicateDeclarationErrorsForSymbols(source, message, symbolName, target);
addDuplicateDeclarationErrorsForSymbols(target, message, symbolName, source);
}
return target;
function addDuplicateLocations(locs: Node[], symbol: Symbol): void {
for (const decl of symbol.declarations) {
pushIfUnique(locs, (getExpandoInitializer(decl, /*isPrototypeAssignment*/ false) ? getNameOfExpando(decl) : getNameOfDeclaration(decl)) || decl);
}
}
}
function addDuplicateDeclarationErrorsForSymbols(target: Symbol, message: DiagnosticMessage, symbolName: string, source: Symbol) {
forEach(target.declarations, node => {
const errorNode = (getExpandoInitializer(node, /*isPrototypeAssignment*/ false) ? getNameOfExpando(node) : getNameOfDeclaration(node)) || node;
addDuplicateDeclarationError(errorNode, message, symbolName, source.declarations && source.declarations[0]);
addDuplicateDeclarationError(errorNode, message, symbolName, source.declarations);
});
}
function addDuplicateDeclarationError(errorNode: Node, message: DiagnosticMessage, symbolName: string, relatedNode: Node | undefined) {
function addDuplicateDeclarationError(errorNode: Node, message: DiagnosticMessage, symbolName: string, relatedNodes: ReadonlyArray<Node> | undefined) {
const err = lookupOrIssueError(errorNode, message, symbolName);
if (relatedNode && length(err.relatedInformation) < 5) {
for (const relatedNode of relatedNodes || emptyArray) {
err.relatedInformation = err.relatedInformation || [];
if (length(err.relatedInformation) >= 5) continue;
addRelatedInfo(err, !length(err.relatedInformation) ? createDiagnosticForNode(relatedNode, Diagnostics._0_was_also_declared_here, symbolName) : createDiagnosticForNode(relatedNode, Diagnostics.and_here));
}
}
@@ -28901,38 +28908,33 @@ namespace ts {
}
}
amalgamatedDuplicates.forEach(({ firstFile, secondFile, firstFileInstances, secondFileInstances }) => {
const conflictingKeys = arrayFrom(firstFileInstances.keys());
amalgamatedDuplicates.forEach(({ firstFile, secondFile, conflictingSymbols }) => {
// If not many things conflict, issue individual errors
if (conflictingKeys.length < 8) {
addErrorsForDuplicates(firstFileInstances, secondFileInstances);
addErrorsForDuplicates(secondFileInstances, firstFileInstances);
return;
if (conflictingSymbols.size < 8) {
conflictingSymbols.forEach(({ isBlockScoped, firstFileLocations, secondFileLocations }, symbolName) => {
const message = isBlockScoped ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 : Diagnostics.Duplicate_identifier_0;
for (const node of firstFileLocations) {
addDuplicateDeclarationError(node, message, symbolName, secondFileLocations);
}
for (const node of secondFileLocations) {
addDuplicateDeclarationError(node, message, symbolName, firstFileLocations);
}
});
}
else {
// Otherwise issue top-level error since the files appear very identical in terms of what they contain
const list = arrayFrom(conflictingSymbols.keys()).join(", ");
diagnostics.add(addRelatedInfo(
createDiagnosticForNode(firstFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list),
createDiagnosticForNode(secondFile, Diagnostics.Conflicts_are_in_this_file)
));
diagnostics.add(addRelatedInfo(
createDiagnosticForNode(secondFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list),
createDiagnosticForNode(firstFile, Diagnostics.Conflicts_are_in_this_file)
));
}
// Otheriwse issue top-level error since the files appear very identical in terms of what they appear
const list = conflictingKeys.join(", ");
diagnostics.add(addRelatedInfo(
createDiagnosticForNode(firstFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list),
createDiagnosticForNode(secondFile, Diagnostics.Conflicts_are_in_this_file)
));
diagnostics.add(addRelatedInfo(
createDiagnosticForNode(secondFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list),
createDiagnosticForNode(firstFile, Diagnostics.Conflicts_are_in_this_file)
));
});
amalgamatedDuplicates = undefined;
function addErrorsForDuplicates(secondFileInstances: Map<{ instances: Node[]; blockScoped: boolean; }>, firstFileInstances: Map<{ instances: Node[]; blockScoped: boolean; }>) {
secondFileInstances.forEach((locations, symbolName) => {
const firstFileEquivalent = firstFileInstances.get(symbolName)!;
const message = locations.blockScoped
? Diagnostics.Cannot_redeclare_block_scoped_variable_0
: Diagnostics.Duplicate_identifier_0;
locations.instances.forEach(node => {
addDuplicateDeclarationError(node, message, symbolName, firstFileEquivalent.instances[0]);
});
});
}
}
function checkExternalEmitHelpers(location: Node, helpers: ExternalEmitHelpers) {

View File

@@ -8369,4 +8369,16 @@ namespace ts {
export function isJsonEqual(a: unknown, b: unknown): boolean {
return a === b || typeof a === "object" && a !== null && typeof b === "object" && b !== null && equalOwnProperties(a as MapLike<unknown>, b as MapLike<unknown>, isJsonEqual);
}
export function getOrUpdate<T>(map: Map<T>, key: string, getDefault: () => T): T {
const got = map.get(key);
if (got === undefined) {
const value = getDefault();
map.set(key, value);
return value;
}
else {
return got;
}
}
}

View File

@@ -0,0 +1,29 @@
/dir/a.ts(1,14): error TS2451: Cannot redeclare block-scoped variable 'x'.
/dir/b.ts(4,18): error TS2451: Cannot redeclare block-scoped variable 'x'.
/dir/b.ts(8,18): error TS2451: Cannot redeclare block-scoped variable 'x'.
==== /dir/a.ts (1 errors) ====
export const x = 0;
~
!!! error TS2451: Cannot redeclare block-scoped variable 'x'.
!!! related TS6203 /dir/b.ts:4:18: 'x' was also declared here.
!!! related TS6204 /dir/b.ts:8:18: and here.
==== /dir/b.ts (2 errors) ====
export {};
declare module "./a" {
export const x = 0;
~
!!! error TS2451: Cannot redeclare block-scoped variable 'x'.
!!! related TS6203 /dir/a.ts:1:14: 'x' was also declared here.
}
declare module "../dir/a" {
export const x = 0;
~
!!! error TS2451: Cannot redeclare block-scoped variable 'x'.
!!! related TS6203 /dir/a.ts:1:14: 'x' was also declared here.
}

View File

@@ -0,0 +1,24 @@
//// [tests/cases/compiler/duplicateIdentifierRelatedSpans_moduleAugmentation.ts] ////
//// [a.ts]
export const x = 0;
//// [b.ts]
export {};
declare module "./a" {
export const x = 0;
}
declare module "../dir/a" {
export const x = 0;
}
//// [a.js]
"use strict";
exports.__esModule = true;
exports.x = 0;
//// [b.js]
"use strict";
exports.__esModule = true;

View File

@@ -0,0 +1,21 @@
=== /dir/a.ts ===
export const x = 0;
>x : Symbol(x, Decl(a.ts, 0, 12))
=== /dir/b.ts ===
export {};
declare module "./a" {
>"./a" : Symbol("/dir/a", Decl(a.ts, 0, 0), Decl(b.ts, 0, 10), Decl(b.ts, 4, 1))
export const x = 0;
>x : Symbol(x, Decl(b.ts, 3, 16))
}
declare module "../dir/a" {
>"../dir/a" : Symbol("/dir/a", Decl(a.ts, 0, 0), Decl(b.ts, 0, 10), Decl(b.ts, 4, 1))
export const x = 0;
>x : Symbol(x, Decl(b.ts, 7, 16))
}

View File

@@ -0,0 +1,24 @@
=== /dir/a.ts ===
export const x = 0;
>x : 0
>0 : 0
=== /dir/b.ts ===
export {};
declare module "./a" {
>"./a" : typeof import("/dir/a")
export const x = 0;
>x : 0
>0 : 0
}
declare module "../dir/a" {
>"../dir/a" : typeof import("/dir/a")
export const x = 0;
>x : 0
>0 : 0
}

View File

@@ -0,0 +1,13 @@
// @Filename: /dir/a.ts
export const x = 0;
// @Filename: /dir/b.ts
export {};
declare module "./a" {
export const x = 0;
}
declare module "../dir/a" {
export const x = 0;
}