Prerequisites
- Understanding of TypeScript interfaces and namespaces π
- Knowledge of module systems and type declarations β‘
- Familiarity with TypeScript compilation process π»
What you'll learn
- Master all types of declaration merging patterns π―
- Safely extend existing types and library definitions ποΈ
- Create flexible and maintainable type architectures π
- Build sophisticated declaration merging systems β¨
π― Introduction
Welcome to the magical world of TypeScriptβs declaration merging! π If TypeScript declarations were like LEGO blocks, then declaration merging is like having the superpower to combine identical blocks automatically - when you have multiple interface declarations with the same name, TypeScript doesnβt throw an error; instead, it intelligently merges them into one super-interface with all the properties and methods from each declaration!
Declaration merging is one of TypeScriptβs most powerful and flexible features, allowing you to extend existing types, patch library definitions, create modular type architectures, and build sophisticated type systems that can grow and evolve over time. Itβs particularly valuable when working with third-party libraries, creating extensible APIs, and building large-scale applications.
By the end of this tutorial, youβll be a declaration merging expert, capable of leveraging this powerful feature to create flexible, maintainable, and extensible type systems that can adapt to any requirement. Letβs master the art of declaration fusion! π
π Understanding Declaration Merging
π€ What Is Declaration Merging?
Declaration merging is TypeScriptβs process of combining multiple declarations with the same name into a single definition. This allows you to extend types across multiple files, add functionality to existing libraries, and create modular type definitions that can be combined in flexible ways.
// π Basic interface merging examples
// First interface declaration
interface User {
id: string;
name: string;
}
// Second interface declaration with the same name
interface User {
email: string;
createdAt: Date;
}
// Third interface declaration adding more properties
interface User {
profile: UserProfile;
preferences: UserPreferences;
}
// TypeScript automatically merges all declarations into one
// The final User interface contains all properties:
// {
// id: string;
// name: string;
// email: string;
// createdAt: Date;
// profile: UserProfile;
// preferences: UserPreferences;
// }
interface UserProfile {
avatar: string;
bio: string;
location: string;
website?: string;
}
interface UserPreferences {
theme: 'light' | 'dark';
language: string;
notifications: NotificationSettings;
privacy: PrivacySettings;
}
interface NotificationSettings {
email: boolean;
push: boolean;
sms: boolean;
digest: 'daily' | 'weekly' | 'monthly' | 'never';
}
interface PrivacySettings {
profileVisibility: 'public' | 'private' | 'friends';
searchable: boolean;
showOnlineStatus: boolean;
allowDirectMessages: boolean;
}
// β¨ Using the merged interface
const createUser = (userData: Partial<User>): User => {
return {
id: generateId(),
name: userData.name || 'Anonymous',
email: userData.email || '',
createdAt: new Date(),
profile: userData.profile || {
avatar: '',
bio: '',
location: ''
},
preferences: userData.preferences || {
theme: 'light',
language: 'en',
notifications: {
email: true,
push: true,
sms: false,
digest: 'weekly'
},
privacy: {
profileVisibility: 'public',
searchable: true,
showOnlineStatus: true,
allowDirectMessages: true
}
}
};
};
// π― Namespace merging
namespace DatabaseOperations {
export interface Connection {
host: string;
port: number;
database: string;
}
export function connect(config: Connection): Promise<void> {
return Promise.resolve();
}
}
// Extending the namespace with additional functionality
namespace DatabaseOperations {
export interface QueryOptions {
limit?: number;
offset?: number;
sort?: string;
filter?: Record<string, any>;
}
export function query<T>(sql: string, options?: QueryOptions): Promise<T[]> {
return Promise.resolve([]);
}
}
// Adding more operations to the namespace
namespace DatabaseOperations {
export interface TransactionOptions {
isolation?: 'read_uncommitted' | 'read_committed' | 'repeatable_read' | 'serializable';
timeout?: number;
readonly?: boolean;
}
export function transaction<T>(
callback: (tx: Transaction) => Promise<T>,
options?: TransactionOptions
): Promise<T> {
// Transaction implementation
return Promise.resolve({} as T);
}
export interface Transaction {
query<T>(sql: string, params?: any[]): Promise<T[]>;
commit(): Promise<void>;
rollback(): Promise<void>;
}
}
// The merged namespace contains all declarations
const dbExample = async () => {
await DatabaseOperations.connect({
host: 'localhost',
port: 5432,
database: 'myapp'
});
const users = await DatabaseOperations.query<User>('SELECT * FROM users', {
limit: 10,
sort: 'created_at DESC'
});
await DatabaseOperations.transaction(async (tx) => {
await tx.query('INSERT INTO logs VALUES (?)', ['User accessed']);
await tx.commit();
});
};
function generateId(): string {
return Math.random().toString(36).substr(2, 9);
}
ποΈ Advanced Declaration Merging Patterns
// π Function and namespace merging
// Function declaration
function APIClient(baseUrl: string): APIClient.Instance {
return new APIClient.ClientImpl(baseUrl);
}
// Namespace with the same name (merges with function)
namespace APIClient {
export interface Instance {
get<T>(path: string, config?: RequestConfig): Promise<APIResponse<T>>;
post<T>(path: string, data?: any, config?: RequestConfig): Promise<APIResponse<T>>;
put<T>(path: string, data?: any, config?: RequestConfig): Promise<APIResponse<T>>;
delete<T>(path: string, config?: RequestConfig): Promise<APIResponse<T>>;
// Advanced methods
upload<T>(path: string, file: File, config?: UploadConfig): Promise<APIResponse<T>>;
download(path: string, config?: DownloadConfig): Promise<Blob>;
batch<T>(requests: BatchRequest[]): Promise<BatchResponse<T>>;
}
export interface RequestConfig {
headers?: Record<string, string>;
timeout?: number;
retries?: number;
cache?: boolean;
validateStatus?: (status: number) => boolean;
}
export interface UploadConfig extends RequestConfig {
onProgress?: (progress: number) => void;
chunkSize?: number;
}
export interface DownloadConfig extends RequestConfig {
onProgress?: (progress: number) => void;
responseType?: 'blob' | 'arraybuffer' | 'stream';
}
export interface APIResponse<T> {
data: T;
status: number;
statusText: string;
headers: Record<string, string>;
config: RequestConfig;
timing: ResponseTiming;
}
export interface ResponseTiming {
start: number;
end: number;
duration: number;
dnsLookup?: number;
tcpConnection?: number;
tlsHandshake?: number;
firstByte?: number;
download?: number;
}
export interface BatchRequest {
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
path: string;
data?: any;
config?: RequestConfig;
id?: string;
}
export interface BatchResponse<T> {
results: Array<{
id?: string;
success: boolean;
data?: T;
error?: APIError;
status: number;
}>;
timing: ResponseTiming;
}
export interface APIError {
code: string;
message: string;
details?: any;
timestamp: Date;
requestId?: string;
}
// Implementation class
export class ClientImpl implements Instance {
constructor(private baseUrl: string) {}
async get<T>(path: string, config?: RequestConfig): Promise<APIResponse<T>> {
return this.request<T>('GET', path, undefined, config);
}
async post<T>(path: string, data?: any, config?: RequestConfig): Promise<APIResponse<T>> {
return this.request<T>('POST', path, data, config);
}
async put<T>(path: string, data?: any, config?: RequestConfig): Promise<APIResponse<T>> {
return this.request<T>('PUT', path, data, config);
}
async delete<T>(path: string, config?: RequestConfig): Promise<APIResponse<T>> {
return this.request<T>('DELETE', path, undefined, config);
}
async upload<T>(path: string, file: File, config?: UploadConfig): Promise<APIResponse<T>> {
// Upload implementation with progress tracking
return this.request<T>('POST', path, file, config);
}
async download(path: string, config?: DownloadConfig): Promise<Blob> {
// Download implementation
const response = await this.request<ArrayBuffer>('GET', path, undefined, config);
return new Blob([response.data]);
}
async batch<T>(requests: BatchRequest[]): Promise<BatchResponse<T>> {
// Batch request implementation
const results = await Promise.allSettled(
requests.map(req => this.request<T>(req.method, req.path, req.data, req.config))
);
return {
results: results.map((result, index) => ({
id: requests[index].id,
success: result.status === 'fulfilled',
data: result.status === 'fulfilled' ? result.value.data : undefined,
error: result.status === 'rejected' ? result.reason : undefined,
status: result.status === 'fulfilled' ? result.value.status : 500
})),
timing: {
start: Date.now(),
end: Date.now(),
duration: 0
}
};
}
private async request<T>(
method: string,
path: string,
data?: any,
config?: RequestConfig
): Promise<APIResponse<T>> {
const start = Date.now();
// Request implementation
const response = {
data: {} as T,
status: 200,
statusText: 'OK',
headers: {},
config: config || {},
timing: {
start,
end: Date.now(),
duration: Date.now() - start
}
};
return response;
}
}
// Static methods and properties
export const version = '1.0.0';
export const defaults: RequestConfig = {
timeout: 30000,
retries: 3,
cache: true,
validateStatus: (status) => status >= 200 && status < 300
};
export function createInstance(baseUrl: string, config?: RequestConfig): Instance {
const instance = new ClientImpl(baseUrl);
// Apply default config
return instance;
}
}
// Now APIClient is both a function and a namespace
const client = APIClient('https://api.example.com');
const customClient = APIClient.createInstance('https://api.example.com', {
timeout: 60000,
headers: { 'Authorization': 'Bearer token' }
});
// π§ Class and namespace merging
class Logger {
constructor(private name: string) {}
log(message: string): void {
console.log(`[${this.name}] ${message}`);
}
error(message: string, error?: Error): void {
console.error(`[${this.name}] ERROR: ${message}`, error);
}
}
namespace Logger {
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
export interface LogEntry {
timestamp: Date;
level: LogLevel;
message: string;
metadata?: Record<string, any>;
source: string;
}
export interface LoggerConfig {
level: LogLevel;
format: 'json' | 'text';
output: 'console' | 'file' | 'remote';
filters?: LogFilter[];
}
export interface LogFilter {
level?: LogLevel;
source?: string;
pattern?: RegExp;
exclude?: boolean;
}
// Static factory methods
export function create(name: string, config?: LoggerConfig): Logger {
const logger = new Logger(name);
// Apply configuration
return logger;
}
export function getLogger(name: string): Logger {
return loggerInstances.get(name) || create(name);
}
// Global logger registry
const loggerInstances = new Map<string, Logger>();
export function configure(config: LoggerConfig): void {
globalConfig = { ...globalConfig, ...config };
}
export function setGlobalLevel(level: LogLevel): void {
globalConfig.level = level;
}
let globalConfig: LoggerConfig = {
level: 'info',
format: 'text',
output: 'console'
};
}
// Usage of merged class and namespace
const appLogger = Logger.create('App', {
level: 'debug',
format: 'json',
output: 'file'
});
Logger.setGlobalLevel('warn');
const serviceLogger = Logger.getLogger('Service');
π οΈ Building a Declaration Merging Management System
Letβs create a comprehensive system for managing and tracking declaration merging:
// ποΈ Declaration Merging Analysis and Management System
namespace DeclarationMerging {
// π Core interfaces for tracking merging
export interface MergeRegistry {
declarations: Map<string, DeclarationInfo[]>;
mergedTypes: Map<string, MergedTypeInfo>;
conflicts: MergeConflict[];
metrics: MergeMetrics;
}
export interface DeclarationInfo {
name: string;
type: DeclarationType;
source: SourceLocation;
properties: PropertyInfo[];
methods: MethodInfo[];
callSignatures: CallSignatureInfo[];
constructSignatures: ConstructSignatureInfo[];
indexSignatures: IndexSignatureInfo[];
generics: GenericInfo[];
extends: string[];
implements: string[];
accessibility: 'public' | 'private' | 'protected';
namespace?: string;
module?: string;
exported: boolean;
ambient: boolean;
createdAt: Date;
}
export type DeclarationType =
| 'interface'
| 'namespace'
| 'class'
| 'function'
| 'variable'
| 'type'
| 'enum'
| 'module';
export interface SourceLocation {
file: string;
line: number;
column: number;
length: number;
}
export interface PropertyInfo {
name: string;
type: string;
optional: boolean;
readonly: boolean;
static: boolean;
accessibility: 'public' | 'private' | 'protected';
decorators: string[];
documentation?: string;
}
export interface MethodInfo {
name: string;
signature: string;
returnType: string;
parameters: ParameterInfo[];
overloads: MethodOverload[];
static: boolean;
abstract: boolean;
accessibility: 'public' | 'private' | 'protected';
decorators: string[];
documentation?: string;
}
export interface ParameterInfo {
name: string;
type: string;
optional: boolean;
rest: boolean;
defaultValue?: string;
}
export interface MethodOverload {
signature: string;
parameters: ParameterInfo[];
returnType: string;
}
export interface CallSignatureInfo {
parameters: ParameterInfo[];
returnType: string;
generics: GenericInfo[];
}
export interface ConstructSignatureInfo {
parameters: ParameterInfo[];
returnType: string;
generics: GenericInfo[];
}
export interface IndexSignatureInfo {
keyType: string;
valueType: string;
readonly: boolean;
}
export interface GenericInfo {
name: string;
constraint?: string;
defaultType?: string;
variance?: 'covariant' | 'contravariant' | 'invariant';
}
export interface MergedTypeInfo {
name: string;
finalType: DeclarationType;
mergedFrom: string[];
mergeStrategy: MergeStrategy;
conflicts: MergeConflict[];
properties: MergedPropertyInfo[];
methods: MergedMethodInfo[];
signatures: MergedSignatureInfo[];
success: boolean;
warnings: string[];
createdAt: Date;
}
export type MergeStrategy =
| 'union'
| 'intersection'
| 'override'
| 'extend'
| 'concatenate'
| 'conflict';
export interface MergedPropertyInfo extends PropertyInfo {
sourceDeclarations: string[];
mergeStrategy: MergeStrategy;
conflictResolution?: ConflictResolution;
}
export interface MergedMethodInfo extends MethodInfo {
sourceDeclarations: string[];
mergeStrategy: MergeStrategy;
conflictResolution?: ConflictResolution;
}
export interface MergedSignatureInfo {
type: 'call' | 'construct' | 'index';
signature: string;
sourceDeclarations: string[];
mergeStrategy: MergeStrategy;
}
export interface MergeConflict {
type: ConflictType;
severity: 'error' | 'warning' | 'info';
description: string;
affectedDeclarations: string[];
resolution?: ConflictResolution;
autoResolvable: boolean;
}
export type ConflictType =
| 'property_type_mismatch'
| 'method_signature_conflict'
| 'accessibility_conflict'
| 'modifier_conflict'
| 'generic_constraint_conflict'
| 'inheritance_conflict';
export interface ConflictResolution {
strategy: ResolutionStrategy;
chosen: string;
reason: string;
automatic: boolean;
}
export type ResolutionStrategy =
| 'first_wins'
| 'last_wins'
| 'most_specific'
| 'manual_choice'
| 'union_types'
| 'overload_signatures';
export interface MergeMetrics {
totalDeclarations: number;
successfulMerges: number;
failedMerges: number;
conflictsResolved: number;
conflictsPending: number;
averageMergeTime: number;
mostMergedType: string;
lastAnalysis: Date;
}
// π§ Declaration Merge Analyzer
export class MergeAnalyzer {
private registry: MergeRegistry;
private conflictResolver: ConflictResolver;
private mergeStrategies: Map<string, MergeStrategyHandler>;
constructor() {
this.registry = this.initializeRegistry();
this.conflictResolver = new ConflictResolver();
this.mergeStrategies = this.createMergeStrategies();
}
// π Analyze declaration merging
analyzeDeclarations(declarations: DeclarationInfo[]): MergeAnalysisResult {
console.log(`π Analyzing ${declarations.length} declarations for merging`);
const groupedDeclarations = this.groupDeclarationsByName(declarations);
const mergeResults: MergedTypeInfo[] = [];
const conflicts: MergeConflict[] = [];
for (const [name, declarationGroup] of groupedDeclarations) {
console.log(`π Analyzing merging for: ${name} (${declarationGroup.length} declarations)`);
const mergeResult = this.analyzeSingleMerge(name, declarationGroup);
mergeResults.push(mergeResult);
conflicts.push(...mergeResult.conflicts);
}
this.updateRegistry(mergeResults, conflicts);
return {
mergedTypes: mergeResults,
conflicts,
metrics: this.calculateMetrics(),
suggestions: this.generateSuggestions(mergeResults, conflicts),
generatedAt: new Date()
};
}
// π Analyze a single merge group
private analyzeSingleMerge(name: string, declarations: DeclarationInfo[]): MergedTypeInfo {
console.log(`π Analyzing merge for ${name} with ${declarations.length} declarations`);
if (declarations.length === 1) {
return this.createSingleDeclarationResult(name, declarations[0]);
}
// Check merge compatibility
const compatibility = this.checkMergeCompatibility(declarations);
if (!compatibility.compatible) {
return this.createFailedMergeResult(name, declarations, compatibility.conflicts);
}
// Perform the merge
const mergeStrategy = this.determineMergeStrategy(declarations);
const mergedProperties = this.mergeProperties(declarations);
const mergedMethods = this.mergeMethods(declarations);
const mergedSignatures = this.mergeSignatures(declarations);
// Detect conflicts
const conflicts = this.detectConflicts(declarations, mergedProperties, mergedMethods);
return {
name,
finalType: this.determineFinalType(declarations),
mergedFrom: declarations.map(d => `${d.source.file}:${d.source.line}`),
mergeStrategy,
conflicts,
properties: mergedProperties,
methods: mergedMethods,
signatures: mergedSignatures,
success: conflicts.filter(c => c.severity === 'error').length === 0,
warnings: conflicts.filter(c => c.severity === 'warning').map(c => c.description),
createdAt: new Date()
};
}
// π§ Merge properties from multiple declarations
private mergeProperties(declarations: DeclarationInfo[]): MergedPropertyInfo[] {
const propertyMap = new Map<string, MergedPropertyInfo>();
for (const declaration of declarations) {
for (const property of declaration.properties) {
const existing = propertyMap.get(property.name);
if (!existing) {
propertyMap.set(property.name, {
...property,
sourceDeclarations: [`${declaration.source.file}:${declaration.source.line}`],
mergeStrategy: 'extend'
});
} else {
// Handle property conflicts
const mergeResult = this.mergeProperty(existing, property, declaration);
propertyMap.set(property.name, mergeResult);
}
}
}
return Array.from(propertyMap.values());
}
// π§ Merge methods from multiple declarations
private mergeMethods(declarations: DeclarationInfo[]): MergedMethodInfo[] {
const methodMap = new Map<string, MergedMethodInfo>();
for (const declaration of declarations) {
for (const method of declaration.methods) {
const existing = methodMap.get(method.name);
if (!existing) {
methodMap.set(method.name, {
...method,
sourceDeclarations: [`${declaration.source.file}:${declaration.source.line}`],
mergeStrategy: 'extend'
});
} else {
// Handle method overloads
const mergeResult = this.mergeMethod(existing, method, declaration);
methodMap.set(method.name, mergeResult);
}
}
}
return Array.from(methodMap.values());
}
// π§ Merge signatures from multiple declarations
private mergeSignatures(declarations: DeclarationInfo[]): MergedSignatureInfo[] {
const signatures: MergedSignatureInfo[] = [];
for (const declaration of declarations) {
// Merge call signatures
for (const callSig of declaration.callSignatures) {
signatures.push({
type: 'call',
signature: this.formatCallSignature(callSig),
sourceDeclarations: [`${declaration.source.file}:${declaration.source.line}`],
mergeStrategy: 'concatenate'
});
}
// Merge construct signatures
for (const constructSig of declaration.constructSignatures) {
signatures.push({
type: 'construct',
signature: this.formatConstructSignature(constructSig),
sourceDeclarations: [`${declaration.source.file}:${declaration.source.line}`],
mergeStrategy: 'concatenate'
});
}
// Merge index signatures
for (const indexSig of declaration.indexSignatures) {
signatures.push({
type: 'index',
signature: this.formatIndexSignature(indexSig),
sourceDeclarations: [`${declaration.source.file}:${declaration.source.line}`],
mergeStrategy: 'union'
});
}
}
return signatures;
}
// π¨ Detect conflicts in merged declarations
private detectConflicts(
declarations: DeclarationInfo[],
properties: MergedPropertyInfo[],
methods: MergedMethodInfo[]
): MergeConflict[] {
const conflicts: MergeConflict[] = [];
// Property type conflicts
for (const property of properties) {
if (property.sourceDeclarations.length > 1) {
const typeConflict = this.checkPropertyTypeConflict(property, declarations);
if (typeConflict) {
conflicts.push(typeConflict);
}
}
}
// Method signature conflicts
for (const method of methods) {
if (method.sourceDeclarations.length > 1) {
const signatureConflict = this.checkMethodSignatureConflict(method, declarations);
if (signatureConflict) {
conflicts.push(signatureConflict);
}
}
}
// Accessibility conflicts
const accessibilityConflicts = this.checkAccessibilityConflicts(declarations);
conflicts.push(...accessibilityConflicts);
return conflicts;
}
// π Generate merge suggestions
generateSuggestions(mergedTypes: MergedTypeInfo[], conflicts: MergeConflict[]): MergeSuggestion[] {
const suggestions: MergeSuggestion[] = [];
// Suggest automatic conflict resolutions
for (const conflict of conflicts) {
if (conflict.autoResolvable && !conflict.resolution) {
suggestions.push({
type: 'auto_resolve_conflict',
description: `Automatically resolve ${conflict.type} conflict`,
impact: 'low',
effort: 'minimal',
conflictId: this.getConflictId(conflict),
implementation: this.generateConflictResolution(conflict)
});
}
}
// Suggest merge optimizations
for (const mergedType of mergedTypes) {
if (mergedType.success && mergedType.methods.length > 10) {
suggestions.push({
type: 'optimize_interface',
description: `Consider splitting ${mergedType.name} into smaller interfaces`,
impact: 'medium',
effort: 'moderate',
implementation: `Split into: ${this.suggestInterfaceSplit(mergedType)}`
});
}
}
// Suggest documentation improvements
const undocumentedTypes = mergedTypes.filter(t =>
t.properties.some(p => !p.documentation) ||
t.methods.some(m => !m.documentation)
);
if (undocumentedTypes.length > 0) {
suggestions.push({
type: 'improve_documentation',
description: `Add documentation to ${undocumentedTypes.length} merged types`,
impact: 'high',
effort: 'moderate',
implementation: 'Add JSDoc comments to properties and methods'
});
}
return suggestions;
}
// π§ Helper methods
private initializeRegistry(): MergeRegistry {
return {
declarations: new Map(),
mergedTypes: new Map(),
conflicts: [],
metrics: {
totalDeclarations: 0,
successfulMerges: 0,
failedMerges: 0,
conflictsResolved: 0,
conflictsPending: 0,
averageMergeTime: 0,
mostMergedType: '',
lastAnalysis: new Date()
}
};
}
private createMergeStrategies(): Map<string, MergeStrategyHandler> {
return new Map([
['interface', new InterfaceMergeStrategy()],
['namespace', new NamespaceMergeStrategy()],
['class', new ClassMergeStrategy()],
['function', new FunctionMergeStrategy()]
]);
}
private groupDeclarationsByName(declarations: DeclarationInfo[]): Map<string, DeclarationInfo[]> {
const groups = new Map<string, DeclarationInfo[]>();
for (const declaration of declarations) {
const key = this.getDeclarationKey(declaration);
const group = groups.get(key) || [];
group.push(declaration);
groups.set(key, group);
}
return groups;
}
private getDeclarationKey(declaration: DeclarationInfo): string {
return `${declaration.namespace || 'global'}.${declaration.name}`;
}
private checkMergeCompatibility(declarations: DeclarationInfo[]): CompatibilityResult {
const types = new Set(declarations.map(d => d.type));
// Check if all declarations are of compatible types
if (types.size > 1) {
const compatibleCombinations = [
new Set(['interface', 'namespace']),
new Set(['function', 'namespace']),
new Set(['class', 'namespace'])
];
const isCompatible = compatibleCombinations.some(combo =>
types.size === combo.size && [...types].every(type => combo.has(type))
);
if (!isCompatible) {
return {
compatible: false,
conflicts: [{
type: 'incompatible_declaration_types',
severity: 'error',
description: `Cannot merge declarations of types: ${[...types].join(', ')}`,
affectedDeclarations: declarations.map(d => `${d.source.file}:${d.source.line}`),
autoResolvable: false
}]
};
}
}
return { compatible: true, conflicts: [] };
}
private determineMergeStrategy(declarations: DeclarationInfo[]): MergeStrategy {
const types = new Set(declarations.map(d => d.type));
if (types.has('interface')) return 'extend';
if (types.has('namespace')) return 'concatenate';
if (types.has('class')) return 'override';
if (types.has('function')) return 'overload';
return 'union';
}
private determineFinalType(declarations: DeclarationInfo[]): DeclarationType {
const types = declarations.map(d => d.type);
// Priority order for determining final type
const typePriority: DeclarationType[] = [
'class', 'interface', 'namespace', 'function', 'type', 'enum', 'variable', 'module'
];
for (const type of typePriority) {
if (types.includes(type)) {
return type;
}
}
return types[0];
}
private createSingleDeclarationResult(name: string, declaration: DeclarationInfo): MergedTypeInfo {
return {
name,
finalType: declaration.type,
mergedFrom: [`${declaration.source.file}:${declaration.source.line}`],
mergeStrategy: 'extend',
conflicts: [],
properties: declaration.properties.map(p => ({
...p,
sourceDeclarations: [`${declaration.source.file}:${declaration.source.line}`],
mergeStrategy: 'extend'
})),
methods: declaration.methods.map(m => ({
...m,
sourceDeclarations: [`${declaration.source.file}:${declaration.source.line}`],
mergeStrategy: 'extend'
})),
signatures: [],
success: true,
warnings: [],
createdAt: new Date()
};
}
private createFailedMergeResult(
name: string,
declarations: DeclarationInfo[],
conflicts: MergeConflict[]
): MergedTypeInfo {
return {
name,
finalType: declarations[0].type,
mergedFrom: declarations.map(d => `${d.source.file}:${d.source.line}`),
mergeStrategy: 'conflict',
conflicts,
properties: [],
methods: [],
signatures: [],
success: false,
warnings: conflicts.map(c => c.description),
createdAt: new Date()
};
}
private mergeProperty(
existing: MergedPropertyInfo,
newProperty: PropertyInfo,
declaration: DeclarationInfo
): MergedPropertyInfo {
const sourceDeclaration = `${declaration.source.file}:${declaration.source.line}`;
if (existing.type !== newProperty.type) {
return {
...existing,
type: `${existing.type} | ${newProperty.type}`,
sourceDeclarations: [...existing.sourceDeclarations, sourceDeclaration],
mergeStrategy: 'union',
conflictResolution: {
strategy: 'union_types',
chosen: 'union',
reason: 'Different types merged into union',
automatic: true
}
};
}
return {
...existing,
sourceDeclarations: [...existing.sourceDeclarations, sourceDeclaration],
mergeStrategy: 'extend'
};
}
private mergeMethod(
existing: MergedMethodInfo,
newMethod: MethodInfo,
declaration: DeclarationInfo
): MergedMethodInfo {
const sourceDeclaration = `${declaration.source.file}:${declaration.source.line}`;
// Add as overload if signatures differ
if (existing.signature !== newMethod.signature) {
return {
...existing,
overloads: [
...existing.overloads,
{
signature: newMethod.signature,
parameters: newMethod.parameters,
returnType: newMethod.returnType
}
],
sourceDeclarations: [...existing.sourceDeclarations, sourceDeclaration],
mergeStrategy: 'overload_signatures'
};
}
return {
...existing,
sourceDeclarations: [...existing.sourceDeclarations, sourceDeclaration],
mergeStrategy: 'extend'
};
}
private formatCallSignature(signature: CallSignatureInfo): string {
const generics = signature.generics.length > 0 ?
`<${signature.generics.map(g => g.name).join(', ')}>` : '';
const params = signature.parameters.map(p =>
`${p.name}${p.optional ? '?' : ''}: ${p.type}`
).join(', ');
return `${generics}(${params}): ${signature.returnType}`;
}
private formatConstructSignature(signature: ConstructSignatureInfo): string {
const generics = signature.generics.length > 0 ?
`<${signature.generics.map(g => g.name).join(', ')}>` : '';
const params = signature.parameters.map(p =>
`${p.name}${p.optional ? '?' : ''}: ${p.type}`
).join(', ');
return `new ${generics}(${params}): ${signature.returnType}`;
}
private formatIndexSignature(signature: IndexSignatureInfo): string {
const readonly = signature.readonly ? 'readonly ' : '';
return `${readonly}[key: ${signature.keyType}]: ${signature.valueType}`;
}
private checkPropertyTypeConflict(
property: MergedPropertyInfo,
declarations: DeclarationInfo[]
): MergeConflict | null {
// Implementation for checking property type conflicts
return null;
}
private checkMethodSignatureConflict(
method: MergedMethodInfo,
declarations: DeclarationInfo[]
): MergeConflict | null {
// Implementation for checking method signature conflicts
return null;
}
private checkAccessibilityConflicts(declarations: DeclarationInfo[]): MergeConflict[] {
// Implementation for checking accessibility conflicts
return [];
}
private updateRegistry(mergeResults: MergedTypeInfo[], conflicts: MergeConflict[]): void {
// Update the registry with new merge results
for (const result of mergeResults) {
this.registry.mergedTypes.set(result.name, result);
}
this.registry.conflicts.push(...conflicts);
this.registry.metrics = this.calculateMetrics();
}
private calculateMetrics(): MergeMetrics {
const mergedTypes = Array.from(this.registry.mergedTypes.values());
const successful = mergedTypes.filter(t => t.success).length;
const failed = mergedTypes.filter(t => !t.success).length;
return {
totalDeclarations: this.registry.declarations.size,
successfulMerges: successful,
failedMerges: failed,
conflictsResolved: this.registry.conflicts.filter(c => c.resolution).length,
conflictsPending: this.registry.conflicts.filter(c => !c.resolution).length,
averageMergeTime: 0, // Calculate from timing data
mostMergedType: this.findMostMergedType(mergedTypes),
lastAnalysis: new Date()
};
}
private findMostMergedType(mergedTypes: MergedTypeInfo[]): string {
return mergedTypes.reduce((max, current) =>
current.mergedFrom.length > max.mergedFrom.length ? current : max,
mergedTypes[0]
)?.name || '';
}
private getConflictId(conflict: MergeConflict): string {
return `${conflict.type}_${conflict.affectedDeclarations.join('_')}`;
}
private generateConflictResolution(conflict: MergeConflict): string {
// Generate automatic resolution implementation
return `Resolve ${conflict.type} using ${conflict.resolution?.strategy || 'auto'} strategy`;
}
private suggestInterfaceSplit(mergedType: MergedTypeInfo): string {
// Suggest how to split large interfaces
return `${mergedType.name}Core, ${mergedType.name}Methods, ${mergedType.name}Events`;
}
}
// π§ Supporting classes and interfaces
abstract class MergeStrategyHandler {
abstract canMerge(declarations: DeclarationInfo[]): boolean;
abstract merge(declarations: DeclarationInfo[]): MergedTypeInfo;
}
class InterfaceMergeStrategy extends MergeStrategyHandler {
canMerge(declarations: DeclarationInfo[]): boolean {
return declarations.every(d => d.type === 'interface');
}
merge(declarations: DeclarationInfo[]): MergedTypeInfo {
// Interface-specific merging logic
return {} as MergedTypeInfo;
}
}
class NamespaceMergeStrategy extends MergeStrategyHandler {
canMerge(declarations: DeclarationInfo[]): boolean {
return declarations.every(d => d.type === 'namespace');
}
merge(declarations: DeclarationInfo[]): MergedTypeInfo {
// Namespace-specific merging logic
return {} as MergedTypeInfo;
}
}
class ClassMergeStrategy extends MergeStrategyHandler {
canMerge(declarations: DeclarationInfo[]): boolean {
return declarations.some(d => d.type === 'class') &&
declarations.some(d => d.type === 'namespace');
}
merge(declarations: DeclarationInfo[]): MergedTypeInfo {
// Class + namespace merging logic
return {} as MergedTypeInfo;
}
}
class FunctionMergeStrategy extends MergeStrategyHandler {
canMerge(declarations: DeclarationInfo[]): boolean {
return declarations.some(d => d.type === 'function') &&
declarations.some(d => d.type === 'namespace');
}
merge(declarations: DeclarationInfo[]): MergedTypeInfo {
// Function + namespace merging logic
return {} as MergedTypeInfo;
}
}
class ConflictResolver {
resolveConflict(conflict: MergeConflict): ConflictResolution | null {
switch (conflict.type) {
case 'property_type_mismatch':
return this.resolvePropertyTypeConflict(conflict);
case 'method_signature_conflict':
return this.resolveMethodSignatureConflict(conflict);
case 'accessibility_conflict':
return this.resolveAccessibilityConflict(conflict);
default:
return null;
}
}
private resolvePropertyTypeConflict(conflict: MergeConflict): ConflictResolution {
return {
strategy: 'union_types',
chosen: 'union',
reason: 'Create union type to accommodate all variants',
automatic: true
};
}
private resolveMethodSignatureConflict(conflict: MergeConflict): ConflictResolution {
return {
strategy: 'overload_signatures',
chosen: 'overloads',
reason: 'Create method overloads for different signatures',
automatic: true
};
}
private resolveAccessibilityConflict(conflict: MergeConflict): ConflictResolution {
return {
strategy: 'most_specific',
chosen: 'public',
reason: 'Use most permissive accessibility level',
automatic: false
};
}
}
// π Supporting interfaces
interface CompatibilityResult {
compatible: boolean;
conflicts: MergeConflict[];
}
interface MergeAnalysisResult {
mergedTypes: MergedTypeInfo[];
conflicts: MergeConflict[];
metrics: MergeMetrics;
suggestions: MergeSuggestion[];
generatedAt: Date;
}
interface MergeSuggestion {
type: 'auto_resolve_conflict' | 'optimize_interface' | 'improve_documentation' | 'refactor_types';
description: string;
impact: 'low' | 'medium' | 'high';
effort: 'minimal' | 'moderate' | 'significant';
conflictId?: string;
implementation: string;
}
}
// π Usage examples with the management system
const mergeAnalyzer = new DeclarationMerging.MergeAnalyzer();
// Example declarations to analyze
const sampleDeclarations: DeclarationMerging.DeclarationInfo[] = [
{
name: 'User',
type: 'interface',
source: { file: 'user.ts', line: 1, column: 1, length: 100 },
properties: [
{
name: 'id',
type: 'string',
optional: false,
readonly: true,
static: false,
accessibility: 'public',
decorators: []
}
],
methods: [],
callSignatures: [],
constructSignatures: [],
indexSignatures: [],
generics: [],
extends: [],
implements: [],
accessibility: 'public',
exported: true,
ambient: false,
createdAt: new Date()
},
{
name: 'User',
type: 'interface',
source: { file: 'user-profile.ts', line: 5, column: 1, length: 200 },
properties: [
{
name: 'profile',
type: 'UserProfile',
optional: false,
readonly: false,
static: false,
accessibility: 'public',
decorators: []
}
],
methods: [],
callSignatures: [],
constructSignatures: [],
indexSignatures: [],
generics: [],
extends: [],
implements: [],
accessibility: 'public',
exported: true,
ambient: false,
createdAt: new Date()
}
];
// Analyze the declarations
const analysisResult = mergeAnalyzer.analyzeDeclarations(sampleDeclarations);
console.log('Merge analysis result:', analysisResult);
π¨ Real-World Declaration Merging Examples
// π Extending popular libraries with declaration merging
// Extending Express.js Request interface across multiple files
// File: express-user.d.ts
declare module 'express-serve-static-core' {
interface Request {
user?: {
id: string;
email: string;
role: 'admin' | 'user' | 'moderator';
};
}
}
// File: express-session.d.ts
declare module 'express-serve-static-core' {
interface Request {
sessionId: string;
sessionData: SessionData;
}
interface Response {
sendSuccess<T>(data: T, message?: string): Response;
sendError(error: string, statusCode?: number): Response;
}
}
interface SessionData {
startTime: Date;
lastActivity: Date;
ipAddress: string;
userAgent: string;
preferences: UserPreferences;
}
interface UserPreferences {
language: string;
timezone: string;
theme: 'light' | 'dark';
notifications: boolean;
}
// File: express-validation.d.ts
declare module 'express-serve-static-core' {
interface Request {
validatedBody?: any;
validationErrors?: ValidationError[];
validate<T>(schema: ValidationSchema<T>): Promise<T>;
}
}
interface ValidationError {
field: string;
message: string;
value: any;
code: string;
}
interface ValidationSchema<T> {
[K in keyof T]: FieldValidator<T[K]>;
}
interface FieldValidator<T> {
type: string;
required?: boolean;
min?: number;
max?: number;
pattern?: RegExp;
custom?: (value: T) => boolean | Promise<boolean>;
}
// Now all these extensions are merged into a single Request interface
// Usage in middleware:
const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
// All merged properties are available with full type safety
console.log('User:', req.user?.email);
console.log('Session ID:', req.sessionId);
console.log('Validation errors:', req.validationErrors);
if (!req.user) {
return res.sendError('Authentication required', 401);
}
next();
};
// π― Complex namespace merging across modules
// File: database-core.ts
namespace DatabaseAPI {
export interface Connection {
host: string;
port: number;
database: string;
ssl?: boolean;
}
export interface QueryResult<T> {
rows: T[];
count: number;
duration: number;
}
export function connect(config: Connection): Promise<Client> {
return Promise.resolve(new ClientImpl(config));
}
class ClientImpl implements Client {
constructor(private config: Connection) {}
async query<T>(sql: string, params?: any[]): Promise<QueryResult<T>> {
return {
rows: [],
count: 0,
duration: 0
};
}
async close(): Promise<void> {
// Close connection
}
}
export interface Client {
query<T>(sql: string, params?: any[]): Promise<QueryResult<T>>;
close(): Promise<void>;
}
}
// File: database-transactions.ts
namespace DatabaseAPI {
export interface TransactionOptions {
isolation?: IsolationLevel;
timeout?: number;
readOnly?: boolean;
}
export type IsolationLevel =
| 'READ_UNCOMMITTED'
| 'READ_COMMITTED'
| 'REPEATABLE_READ'
| 'SERIALIZABLE';
export interface Transaction extends Client {
commit(): Promise<void>;
rollback(): Promise<void>;
savepoint(name: string): Promise<void>;
rollbackToSavepoint(name: string): Promise<void>;
}
// Extend the existing Client interface
export interface Client {
transaction<T>(
callback: (tx: Transaction) => Promise<T>,
options?: TransactionOptions
): Promise<T>;
}
export function createTransaction(client: Client, options?: TransactionOptions): Transaction {
return new TransactionImpl(client, options);
}
class TransactionImpl implements Transaction {
constructor(
private client: Client,
private options?: TransactionOptions
) {}
async query<T>(sql: string, params?: any[]): Promise<QueryResult<T>> {
return this.client.query<T>(sql, params);
}
async commit(): Promise<void> {
await this.client.query('COMMIT');
}
async rollback(): Promise<void> {
await this.client.query('ROLLBACK');
}
async savepoint(name: string): Promise<void> {
await this.client.query(`SAVEPOINT ${name}`);
}
async rollbackToSavepoint(name: string): Promise<void> {
await this.client.query(`ROLLBACK TO SAVEPOINT ${name}`);
}
async close(): Promise<void> {
await this.client.close();
}
async transaction<T>(
callback: (tx: Transaction) => Promise<T>,
options?: TransactionOptions
): Promise<T> {
// Nested transaction implementation
return callback(this);
}
}
}
// File: database-migrations.ts
namespace DatabaseAPI {
export interface Migration {
version: string;
name: string;
up: (client: Client) => Promise<void>;
down: (client: Client) => Promise<void>;
}
export interface MigrationRunner {
run(migrations: Migration[]): Promise<MigrationResult>;
rollback(steps?: number): Promise<MigrationResult>;
reset(): Promise<MigrationResult>;
status(): Promise<MigrationStatus>;
}
export interface MigrationResult {
applied: string[];
failed: string[];
errors: MigrationError[];
duration: number;
}
export interface MigrationStatus {
current: string;
pending: string[];
applied: string[];
}
export interface MigrationError {
migration: string;
error: string;
sql?: string;
}
// Extend Client with migration capabilities
export interface Client {
migrate(): MigrationRunner;
}
export function createMigrationRunner(client: Client): MigrationRunner {
return new MigrationRunnerImpl(client);
}
class MigrationRunnerImpl implements MigrationRunner {
constructor(private client: Client) {}
async run(migrations: Migration[]): Promise<MigrationResult> {
const result: MigrationResult = {
applied: [],
failed: [],
errors: [],
duration: 0
};
const start = Date.now();
for (const migration of migrations) {
try {
await migration.up(this.client);
result.applied.push(migration.version);
} catch (error) {
result.failed.push(migration.version);
result.errors.push({
migration: migration.version,
error: error instanceof Error ? error.message : String(error)
});
}
}
result.duration = Date.now() - start;
return result;
}
async rollback(steps: number = 1): Promise<MigrationResult> {
// Rollback implementation
return {
applied: [],
failed: [],
errors: [],
duration: 0
};
}
async reset(): Promise<MigrationResult> {
// Reset implementation
return {
applied: [],
failed: [],
errors: [],
duration: 0
};
}
async status(): Promise<MigrationStatus> {
// Status implementation
return {
current: '001',
pending: [],
applied: ['001']
};
}
}
}
// Now DatabaseAPI namespace contains all merged functionality
const dbUsage = async () => {
const client = await DatabaseAPI.connect({
host: 'localhost',
port: 5432,
database: 'myapp'
});
// Use core functionality
const users = await client.query<User>('SELECT * FROM users');
// Use transaction functionality (merged)
await client.transaction(async (tx) => {
await tx.query('INSERT INTO logs VALUES (?)', ['Transaction started']);
await tx.commit();
});
// Use migration functionality (merged)
const migrationRunner = client.migrate();
await migrationRunner.run([]);
await client.close();
};
// π₯ Advanced function and namespace merging
function Logger(name: string): Logger.Instance {
return new Logger.LoggerImpl(name);
}
namespace Logger {
export interface Instance {
debug(message: string, meta?: any): void;
info(message: string, meta?: any): void;
warn(message: string, meta?: any): void;
error(message: string, error?: Error): void;
child(context: Record<string, any>): Instance;
}
export interface LogEntry {
level: LogLevel;
message: string;
timestamp: Date;
context: Record<string, any>;
meta?: any;
}
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
export interface LoggerConfig {
level: LogLevel;
format: 'json' | 'text' | 'structured';
transports: Transport[];
context?: Record<string, any>;
}
export interface Transport {
type: 'console' | 'file' | 'http' | 'syslog';
level?: LogLevel;
config: any;
}
export class LoggerImpl implements Instance {
private context: Record<string, any> = {};
constructor(
private name: string,
private config: LoggerConfig = Logger.defaultConfig
) {}
debug(message: string, meta?: any): void {
this.log('debug', message, meta);
}
info(message: string, meta?: any): void {
this.log('info', message, meta);
}
warn(message: string, meta?: any): void {
this.log('warn', message, meta);
}
error(message: string, error?: Error): void {
this.log('error', message, error);
}
child(context: Record<string, any>): Instance {
return new LoggerImpl(this.name, {
...this.config,
context: { ...this.context, ...context }
});
}
private log(level: LogLevel, message: string, meta?: any): void {
const entry: LogEntry = {
level,
message,
timestamp: new Date(),
context: { ...this.context, logger: this.name },
meta
};
// Process through transports
for (const transport of this.config.transports) {
if (this.shouldLog(level, transport.level)) {
this.writeToTransport(transport, entry);
}
}
}
private shouldLog(level: LogLevel, transportLevel?: LogLevel): boolean {
const levels = ['debug', 'info', 'warn', 'error'];
const currentIndex = levels.indexOf(level);
const minIndex = levels.indexOf(transportLevel || this.config.level);
return currentIndex >= minIndex;
}
private writeToTransport(transport: Transport, entry: LogEntry): void {
// Transport-specific writing logic
switch (transport.type) {
case 'console':
console.log(this.formatEntry(entry));
break;
case 'file':
// File writing logic
break;
case 'http':
// HTTP transport logic
break;
case 'syslog':
// Syslog transport logic
break;
}
}
private formatEntry(entry: LogEntry): string {
switch (this.config.format) {
case 'json':
return JSON.stringify(entry);
case 'text':
return `[${entry.timestamp.toISOString()}] ${entry.level.toUpperCase()}: ${entry.message}`;
case 'structured':
return `${entry.timestamp.toISOString()} | ${entry.level.padEnd(5)} | ${entry.context.logger} | ${entry.message}`;
default:
return entry.message;
}
}
}
export const defaultConfig: LoggerConfig = {
level: 'info',
format: 'text',
transports: [
{
type: 'console',
config: {}
}
]
};
export function configure(config: Partial<LoggerConfig>): void {
Object.assign(defaultConfig, config);
}
export function createLogger(name: string, config?: LoggerConfig): Instance {
return new LoggerImpl(name, config || defaultConfig);
}
export const root = createLogger('root');
}
// Usage of merged function and namespace
const appLogger = Logger('App'); // Function call
const serviceLogger = Logger.createLogger('Service', { // Namespace method
level: 'debug',
format: 'json',
transports: [
{ type: 'console', config: {} },
{ type: 'file', config: { filename: 'app.log' } }
]
});
Logger.configure({ level: 'warn' }); // Namespace static method
const childLogger = appLogger.child({ requestId: '123' }); // Instance method
π§ͺ Testing Declaration Merging
// π§ͺ Comprehensive testing for declaration merging
import { describe, it, expect, beforeEach } from '@jest/globals';
describe('Declaration Merging', () => {
describe('Interface Merging', () => {
it('should merge interface properties correctly', () => {
// Define interfaces in different scopes to test merging
interface TestInterface {
prop1: string;
}
interface TestInterface {
prop2: number;
}
interface TestInterface {
prop3: boolean;
}
// Test that all properties are available
const testObj: TestInterface = {
prop1: 'hello',
prop2: 42,
prop3: true
};
expect(testObj.prop1).toBe('hello');
expect(testObj.prop2).toBe(42);
expect(testObj.prop3).toBe(true);
});
it('should merge interface methods correctly', () => {
interface Calculator {
add(a: number, b: number): number;
}
interface Calculator {
subtract(a: number, b: number): number;
}
interface Calculator {
multiply(a: number, b: number): number;
divide(a: number, b: number): number;
}
class CalculatorImpl implements Calculator {
add(a: number, b: number): number {
return a + b;
}
subtract(a: number, b: number): number {
return a - b;
}
multiply(a: number, b: number): number {
return a * b;
}
divide(a: number, b: number): number {
return a / b;
}
}
const calc = new CalculatorImpl();
expect(calc.add(2, 3)).toBe(5);
expect(calc.subtract(5, 2)).toBe(3);
expect(calc.multiply(3, 4)).toBe(12);
expect(calc.divide(10, 2)).toBe(5);
});
it('should handle method overloads in merged interfaces', () => {
interface Formatter {
format(value: string): string;
}
interface Formatter {
format(value: number): string;
}
interface Formatter {
format(value: Date): string;
}
class FormatterImpl implements Formatter {
format(value: string | number | Date): string {
if (typeof value === 'string') {
return value.toUpperCase();
}
if (typeof value === 'number') {
return value.toFixed(2);
}
if (value instanceof Date) {
return value.toISOString();
}
return String(value);
}
}
const formatter = new FormatterImpl();
expect(formatter.format('hello')).toBe('HELLO');
expect(formatter.format(42.567)).toBe('42.57');
expect(formatter.format(new Date('2023-01-01'))).toContain('2023-01-01');
});
});
describe('Namespace Merging', () => {
it('should merge namespace exports correctly', () => {
namespace TestNamespace {
export const value1 = 'test1';
export function func1(): string {
return 'func1';
}
}
namespace TestNamespace {
export const value2 = 'test2';
export function func2(): string {
return 'func2';
}
}
namespace TestNamespace {
export interface Config {
setting1: string;
setting2: number;
}
export function configure(config: Config): void {
// Configuration logic
}
}
// All exports should be available
expect(TestNamespace.value1).toBe('test1');
expect(TestNamespace.value2).toBe('test2');
expect(TestNamespace.func1()).toBe('func1');
expect(TestNamespace.func2()).toBe('func2');
const config: TestNamespace.Config = {
setting1: 'value',
setting2: 42
};
expect(() => {
TestNamespace.configure(config);
}).not.toThrow();
});
it('should merge nested namespaces correctly', () => {
namespace OuterNamespace {
export namespace Inner {
export const prop1 = 'inner1';
}
}
namespace OuterNamespace {
export namespace Inner {
export const prop2 = 'inner2';
}
export namespace Another {
export const prop3 = 'another';
}
}
expect(OuterNamespace.Inner.prop1).toBe('inner1');
expect(OuterNamespace.Inner.prop2).toBe('inner2');
expect(OuterNamespace.Another.prop3).toBe('another');
});
});
describe('Function and Namespace Merging', () => {
it('should merge function with namespace correctly', () => {
function TestFunction(input: string): string {
return `processed: ${input}`;
}
namespace TestFunction {
export interface Options {
uppercase: boolean;
prefix: string;
}
export function withOptions(input: string, options: Options): string {
let result = input;
if (options.uppercase) {
result = result.toUpperCase();
}
return `${options.prefix}${result}`;
}
export const defaultOptions: Options = {
uppercase: false,
prefix: ''
};
}
// Test function call
expect(TestFunction('hello')).toBe('processed: hello');
// Test namespace properties and methods
expect(TestFunction.withOptions('hello', { uppercase: true, prefix: 'TEST: ' }))
.toBe('TEST: HELLO');
expect(TestFunction.defaultOptions.uppercase).toBe(false);
});
});
describe('Class and Namespace Merging', () => {
it('should merge class with namespace correctly', () => {
class TestClass {
constructor(public value: string) {}
getValue(): string {
return this.value;
}
}
namespace TestClass {
export interface Config {
defaultValue: string;
validator?: (value: string) => boolean;
}
export function create(config: Config): TestClass {
const value = config.defaultValue;
if (config.validator && !config.validator(value)) {
throw new Error('Invalid default value');
}
return new TestClass(value);
}
export function isValid(instance: TestClass): boolean {
return instance.getValue().length > 0;
}
}
// Test class instantiation
const instance1 = new TestClass('direct');
expect(instance1.getValue()).toBe('direct');
// Test namespace factory method
const instance2 = TestClass.create({
defaultValue: 'factory',
validator: (value) => value.length > 5
});
expect(instance2.getValue()).toBe('factory');
// Test namespace utility method
expect(TestClass.isValid(instance1)).toBe(true);
expect(TestClass.isValid(new TestClass(''))).toBe(false);
});
});
describe('Module Augmentation with Declaration Merging', () => {
it('should extend library types correctly', () => {
// Simulate extending a library interface
interface LibraryType {
existingMethod(): string;
}
// Extend the interface (simulating multiple files)
interface LibraryType {
newMethod(): number;
}
interface LibraryType {
anotherMethod(param: boolean): void;
}
class LibraryTypeImpl implements LibraryType {
existingMethod(): string {
return 'existing';
}
newMethod(): number {
return 42;
}
anotherMethod(param: boolean): void {
console.log(`Called with ${param}`);
}
}
const lib = new LibraryTypeImpl();
expect(lib.existingMethod()).toBe('existing');
expect(lib.newMethod()).toBe(42);
expect(() => lib.anotherMethod(true)).not.toThrow();
});
});
describe('Error Cases and Edge Cases', () => {
it('should handle conflicting property types', () => {
// This would cause a TypeScript error in real usage
// but we can test the concept
interface ConflictTest {
prop: string;
}
// In a real scenario, this would conflict:
// interface ConflictTest {
// prop: number; // Error: Subsequent property declarations must have the same type
// }
// Instead, we test union types as a resolution
interface UnionTest {
prop: string | number;
}
const unionObj: UnionTest = { prop: 'string' };
expect(typeof unionObj.prop).toBe('string');
const unionObj2: UnionTest = { prop: 42 };
expect(typeof unionObj2.prop).toBe('number');
});
it('should handle complex inheritance scenarios', () => {
interface Base {
baseMethod(): void;
}
interface Extended extends Base {
extendedMethod(): void;
}
// Merge additional methods into Extended
interface Extended {
additionalMethod(): void;
}
class ExtendedImpl implements Extended {
baseMethod(): void {
console.log('base');
}
extendedMethod(): void {
console.log('extended');
}
additionalMethod(): void {
console.log('additional');
}
}
const obj = new ExtendedImpl();
expect(() => {
obj.baseMethod();
obj.extendedMethod();
obj.additionalMethod();
}).not.toThrow();
});
});
});
// π Performance testing for declaration merging
describe('Declaration Merging Performance', () => {
it('should handle large interface merges efficiently', () => {
// Create a large interface through merging
interface LargeInterface {
prop1: string;
}
// Simulate many merges
for (let i = 2; i <= 100; i++) {
eval(`
interface LargeInterface {
prop${i}: string;
}
`);
}
// Test that TypeScript can handle the merged interface
const testCreation = () => {
const obj: Partial<LargeInterface> = {};
for (let i = 1; i <= 100; i++) {
(obj as any)[`prop${i}`] = `value${i}`;
}
return obj;
};
const start = performance.now();
const largeObj = testCreation();
const end = performance.now();
expect(end - start).toBeLessThan(100); // Should complete quickly
expect(Object.keys(largeObj).length).toBe(100);
});
});
π― Conclusion
Congratulations! Youβve now mastered the sophisticated art of declaration merging in TypeScript! π
Throughout this tutorial, youβve learned how to:
- Master all types of declaration merging including interfaces, namespaces, functions, and classes
- Safely extend existing types and third-party library definitions without breaking compatibility
- Build comprehensive management systems for tracking and analyzing declaration merging in large projects
- Handle complex merging scenarios with conflicts, resolutions, and optimization strategies
- Create flexible type architectures that can grow and evolve through strategic merging
Declaration merging is one of TypeScriptβs most powerful features, enabling you to create extensible, modular type systems that can adapt to changing requirements. The key is to use it thoughtfully, with proper analysis, conflict resolution, and documentation to ensure your merged declarations remain maintainable and understandable.
Remember: declaration merging is about enhancement and extension, not replacement. Always strive to maintain backward compatibility and clear semantic meaning when merging declarations. With the management system and testing strategies youβve learned, you can confidently use declaration merging to create exactly the type system you need while maintaining the highest standards of code quality.
Keep practicing these patterns, and youβll find that declaration merging becomes an invaluable tool for creating flexible, extensible, and maintainable type systems that can adapt to any requirement! π