#!/usr/bin/env pwsh <# .SYNOPSIS Converts C# files from block-scoped to file-scoped namespaces and fixes System namespace conflicts. .DESCRIPTION This script converts all C# files in MediaBrowser.Model from block-scoped namespaces to file-scoped namespaces, and replaces System using directives with global::System to resolve conflicts with MediaBrowser.Model.System namespace. .PARAMETER Path The root path to search for C# files. Defaults to "MediaBrowser.Model" .PARAMETER DryRun If specified, shows what would be changed without actually modifying files .PARAMETER Verbose Shows detailed processing information .PARAMETER BackupFiles Creates .bak files before conversion .EXAMPLE .\ConvertToFileScopedNamespaces.ps1 -DryRun .EXAMPLE .\ConvertToFileScopedNamespaces.ps1 -BackupFiles .EXAMPLE .\ConvertToFileScopedNamespaces.ps1 -Path "MediaBrowser.Model\Dlna" -Verbose #> param( [string]$Path = "MediaBrowser.Model", [switch]$DryRun, [switch]$BackupFiles ) $ErrorActionPreference = "Stop" $VerbosePreference = if ($PSBoundParameters.ContainsKey('Verbose')) { 'Continue' } else { 'SilentlyContinue' } function Get-IndentString { param([string]$Line) if ($Line -match '^(\s+)') { return $Matches[1] } return "" } function Get-IndentLevel { param([string]$IndentString) # Count spaces (4 spaces = 1 level) or tabs (1 tab = 1 level) $spaces = ($IndentString.ToCharArray() | Where-Object { $_ -eq ' ' }).Count $tabs = ($IndentString.ToCharArray() | Where-Object { $_ -eq "`t" }).Count return [Math]::Max($tabs, [Math]::Floor($spaces / 4)) } function Remove-OneIndentLevel { param([string]$Line, [ref]$IndentChar, [ref]$IndentSize) if ([string]::IsNullOrWhiteSpace($Line)) { return $Line } $indent = Get-IndentString -Line $Line if ($indent.Length -eq 0) { return $Line } # Detect indent character and size on first indented line if ($IndentChar.Value -eq $null) { if ($indent[0] -eq "`t") { $IndentChar.Value = "`t" $IndentSize.Value = 1 } else { $IndentChar.Value = ' ' # Detect indent size (usually 4 spaces) $IndentSize.Value = 4 if ($indent.Length -ge 2) { # Try to detect actual indent size for ($i = 1; $i -le 8; $i++) { if ($indent.Length % $i -eq 0) { $IndentSize.Value = $i break } } } } Write-Verbose "Detected indent: '$($IndentChar.Value)' x $($IndentSize.Value)" } # Remove one level of indentation if ($IndentChar.Value -eq "`t") { if ($Line -match '^\t(.*)') { return $Matches[1] } } else { $pattern = "^" + (' ' * $IndentSize.Value) + '(.*)' if ($Line -match $pattern) { return $Matches[1] } } return $Line } function Test-FileScopedNamespace { param([string]$Content) # Check if already using file-scoped namespace (semicolon after namespace declaration) return $Content -match 'namespace\s+[\w\.]+\s*;' } function Test-BlockScopedNamespace { param([string]$Content) # Check for block-scoped namespace return $Content -match 'namespace\s+[\w\.]+\s*\r?\n?\s*\{' } function Add-GlobalPrefix { param([string]$Content) # System namespaces to prefix (ordered from most specific to least) $systemNamespaces = @( 'System\.Xml\.Serialization', 'System\.ComponentModel\.DataAnnotations', 'System\.ComponentModel', 'System\.Collections\.Generic', 'System\.Collections\.Concurrent', 'System\.Collections', 'System\.Diagnostics\.CodeAnalysis', 'System\.Diagnostics', 'System\.Globalization', 'System\.Linq\.Expressions', 'System\.Linq', 'System\.Text\.Json\.Serialization', 'System\.Text\.Json', 'System\.Text\.RegularExpressions', 'System\.Text', 'System\.Threading\.Tasks', 'System\.Threading', 'System\.Runtime\.Serialization', 'System\.Runtime\.CompilerServices', 'System\.Runtime', 'System\.Net\.Http', 'System\.Net\.Sockets', 'System\.Net\.WebSockets', 'System\.Net\.Mime', 'System\.Net', 'System\.IO\.Compression', 'System\.IO', 'System\.Security', 'System' # Must be last to avoid partial matches ) foreach ($ns in $systemNamespaces) { # Don't add global:: if it's already there # Match: "using System.X" but not "using global::System.X" $pattern = '(\s+using\s+)(?!global::)(' + $ns + ')(\s|;)' $replacement = '${1}global::${2}${3}' $Content = $Content -replace $pattern, $replacement } return $Content } function Convert-ToFileScopedNamespace { param( [string]$FilePath, [switch]$DryRun, [switch]$CreateBackup ) $content = Get-Content -Path $FilePath -Raw $originalContent = $content $modified = $false # Check current state $hasFileScopedNS = Test-FileScopedNamespace -Content $content $hasBlockScopedNS = Test-BlockScopedNamespace -Content $content if ($hasFileScopedNS) { Write-Verbose " Already using file-scoped namespace" # But still add global:: prefixes if needed $contentWithGlobal = Add-GlobalPrefix -Content $content if ($contentWithGlobal -ne $content) { $content = $contentWithGlobal $modified = $true Write-Host " [UPDATED] Added global:: prefixes" -ForegroundColor Yellow } else { Write-Host " [SKIP] Already correct" -ForegroundColor Gray return $false } } elseif (-not $hasBlockScopedNS) { Write-Host " [SKIP] No namespace found" -ForegroundColor Gray return $false } else { Write-Verbose " Converting block-scoped to file-scoped namespace" # Extract namespace name and line if ($content -match 'namespace\s+([\w\.]+)\s*\r?\n?\s*\{') { $namespaceName = $Matches[1] Write-Verbose " Namespace: $namespaceName" } else { Write-Warning " Could not parse namespace" return $false } # Step 1: Convert namespace declaration to file-scoped $content = $content -replace '(namespace\s+[\w\.]+)\s*\r?\n?\s*\{', '$1;' $modified = $true # Step 2: Add global:: prefixes to System using directives $content = Add-GlobalPrefix -Content $content # Step 3: Parse into lines for indentation removal $lines = $content -split '\r?\n' $hasWindowsLineEndings = $originalContent -match '\r\n' # Find namespace declaration line $namespaceLineIndex = -1 for ($i = 0; $i -lt $lines.Length; $i++) { if ($lines[$i] -match '^namespace\s+[\w\.]+\s*;') { $namespaceLineIndex = $i break } } if ($namespaceLineIndex -lt 0) { Write-Warning " Could not find namespace declaration after conversion" return $false } # Step 4: Remove one level of indentation from all lines after namespace $indentChar = $null $indentSize = 4 $indentCharRef = [ref]$indentChar $indentSizeRef = [ref]$indentSize for ($i = $namespaceLineIndex + 1; $i -lt $lines.Length; $i++) { $lines[$i] = Remove-OneIndentLevel -Line $lines[$i] -IndentChar $indentCharRef -IndentSize $indentSizeRef } # Step 5: Remove the closing brace of the namespace # Find the last non-empty, non-whitespace line that's just a closing brace $closingBraceIndex = -1 for ($i = $lines.Length - 1; $i -gt $namespaceLineIndex; $i--) { $trimmed = $lines[$i].Trim() if ($trimmed -eq '}') { $closingBraceIndex = $i break } elseif ($trimmed -ne '' -and $trimmed -ne '}') { # Found non-brace content, stop looking break } } if ($closingBraceIndex -ge 0) { Write-Verbose " Removing closing brace at line $closingBraceIndex" $lines = $lines[0..($closingBraceIndex-1)] + $lines[($closingBraceIndex+1)..($lines.Length-1)] } # Step 6: Rejoin lines $lineEnding = if ($hasWindowsLineEndings) { "`r`n" } else { "`n" } $content = ($lines | Where-Object { $_ -ne $null }) -join $lineEnding # Step 7: Clean up trailing whitespace and ensure single newline at end $content = $content.TrimEnd() $content += $lineEnding Write-Host " [CONVERTED] Block-scoped → File-scoped" -ForegroundColor Green } # Apply changes if ($modified -and $content -ne $originalContent) { if ($DryRun) { Write-Host " [DRY-RUN] Would save changes" -ForegroundColor Cyan return $true } if ($CreateBackup) { $backupPath = "$FilePath.bak" Copy-Item -Path $FilePath -Destination $backupPath -Force Write-Verbose " Created backup: $backupPath" } # Write with correct encoding (UTF-8 without BOM for .cs files) [System.IO.File]::WriteAllText($FilePath, $content, [System.Text.UTF8Encoding]::new($false)) return $true } return $false } # Main script Write-Host ("=" * 80) -ForegroundColor Cyan Write-Host "C# File-Scoped Namespace Converter" -ForegroundColor Cyan Write-Host "Fixes MediaBrowser.Model.System namespace conflicts" -ForegroundColor Cyan Write-Host ("=" * 80) -ForegroundColor Cyan Write-Host if ($DryRun) { Write-Host "DRY RUN MODE - No files will be modified" -ForegroundColor Yellow Write-Host } if ($BackupFiles) { Write-Host "Backup mode enabled - .bak files will be created" -ForegroundColor Yellow Write-Host } if (-not (Test-Path $Path)) { Write-Error "❌ Path not found: $Path" exit 1 } # Find all C# files $files = Get-ChildItem -Path $Path -Filter "*.cs" -Recurse | Where-Object { $_.FullName -notmatch "\\obj\\" -and $_.FullName -notmatch "\\bin\\" -and $_.FullName -notmatch "\\Properties\\AssemblyInfo\.cs$" } Write-Host "Found $($files.Count) C# files in $Path" -ForegroundColor Cyan Write-Host $convertedCount = 0 $skippedCount = 0 $errorCount = 0 $updatedCount = 0 foreach ($file in $files) { $relativePath = $file.FullName.Replace((Get-Location).Path, "").TrimStart([char]92, [char]47) Write-Host "[Processing] $relativePath" -ForegroundColor White try { $result = Convert-ToFileScopedNamespace -FilePath $file.FullName -DryRun:$DryRun -CreateBackup:$BackupFiles if ($result) { if ($result -eq "updated") { $updatedCount++ } else { $convertedCount++ } } else { $skippedCount++ } } catch { Write-Host " [ERROR] $_" -ForegroundColor Red Write-Verbose $_.ScriptStackTrace $errorCount++ } } Write-Host Write-Host ("=" * 80) -ForegroundColor Cyan Write-Host "Summary:" -ForegroundColor Cyan Write-Host ("=" * 80) -ForegroundColor Cyan Write-Host " Total files: $($files.Count)" -ForegroundColor White Write-Host " Converted: $convertedCount" -ForegroundColor Green Write-Host " Updated (global::): $updatedCount" -ForegroundColor Yellow Write-Host " Skipped: $skippedCount" -ForegroundColor Gray Write-Host " Errors: $errorCount" -ForegroundColor Red if ($DryRun) { Write-Host Write-Host "This was a DRY RUN. Run without -DryRun to apply changes." -ForegroundColor Yellow } elseif ($convertedCount -gt 0 -or $updatedCount -gt 0) { Write-Host Write-Host "Conversion complete! Next steps:" -ForegroundColor Green Write-Host " 1. Review changes with: git diff" -ForegroundColor White Write-Host " 2. Build project: dotnet build MediaBrowser.Model\MediaBrowser.Model.csproj" -ForegroundColor White Write-Host " 3. If successful: git add . && git commit -m `"Convert to file-scoped namespaces`"" -ForegroundColor White } Write-Host ("=" * 80) -ForegroundColor Cyan exit $errorCount