···11import type { SymbolKind } from '../symbols/types';
2233+const kindRank: Record<SymbolKind, number> = {
44+ class: 3,
55+ enum: 4,
66+ function: 5,
77+ interface: 1,
88+ namespace: 0,
99+ type: 2,
1010+ var: 6,
1111+};
1212+313/**
414 * Returns true if two declarations of given kinds
515 * are allowed to share the same identifier in TypeScript.
616 */
717export function canShareName(a: SymbolKind, b: SymbolKind): boolean {
88- // same-kind always valid for interfaces (merging)
99- if (a === 'interface' && b === 'interface') return true;
1010-1111- // type vs interface merges
1212- if (
1313- (a === 'interface' && b === 'type') ||
1414- (a === 'type' && b === 'interface')
1515- ) {
1616- return false; // TypeScript does NOT merge type-alias with interface.
1717- }
1818-1919- // type vs type = conflict
2020- if (a === 'type' && b === 'type') return false;
2121-2222- // interface vs class = allowed (declare-merge)
2323- if (
2424- (a === 'interface' && b === 'class') ||
2525- (a === 'class' && b === 'interface')
2626- ) {
2727- return true;
2828- }
2929-3030- // enum vs namespace = allowed (merges into value+type)
3131- if (
3232- (a === 'enum' && b === 'namespace') ||
3333- (a === 'namespace' && b === 'enum')
3434- ) {
3535- return true;
3636- }
3737-3838- // class vs namespace = allowed
3939- if (
4040- (a === 'class' && b === 'namespace') ||
4141- (a === 'namespace' && b === 'class')
4242- ) {
4343- return true;
1818+ // sort based on TypeScript merge precedence so `a` is always the weaker merge candidate
1919+ // ensures that asymmetric merges like `type + var` are correctly handled
2020+ if (kindRank[a] > kindRank[b]) {
2121+ [a, b] = [b, a];
4422 }
45234646- // namespace vs namespace = allowed (merging)
4747- if (a === 'namespace' && b === 'namespace') return true;
4848-4949- // enum vs enum = conflict IF values conflict (TypeScript flags duplicates)
5050- if (a === 'enum' && b === 'enum') return false;
5151-5252- // function and namespace merge (namespace can augment function)
5353- if (
5454- (a === 'function' && b === 'namespace') ||
5555- (a === 'namespace' && b === 'function')
5656- ) {
5757- return true;
2424+ switch (a) {
2525+ case 'interface':
2626+ return b === 'class' || b === 'interface';
2727+ case 'namespace':
2828+ return (
2929+ b === 'class' || b === 'enum' || b === 'function' || b === 'namespace'
3030+ );
3131+ case 'type':
3232+ // type can only merge with value-only declarations
3333+ return b === 'function' || b === 'var';
3434+ default:
3535+ return false;
5836 }
5959-6060- // these collide with each other in the value namespace
6161- const valueKinds = new Set<SymbolKind>(['class', 'enum', 'function', 'var']);
6262-6363- const aInValue = valueKinds.has(a);
6464- const bInValue = valueKinds.has(b);
6565-6666- if (aInValue && bInValue) return false;
6767-6868- // type-only declarations do not collide with value-only declarations
6969- const typeKinds = new Set<SymbolKind>(['interface', 'type']);
7070- const aInType = typeKinds.has(a);
7171- const bInType = typeKinds.has(b);
7272-7373- // if one is type-only and the other is value-only, they do NOT collide
7474- if (aInType !== bInType) return true;
7575-7676- return true;
7737}