eng/scripts/Add-CopyrightHeaders.ps1 (235 lines of code) (raw):

#!/bin/env pwsh #Requires -Version 7 # Script to add copyright headers to all files param( [switch]$Force ) $copyrightText = "Copyright (c) Microsoft Corporation." $licenseText = "Licensed under the MIT License." # Define comment style patterns $commentStyles = @{ 'doubleslash' = @{ extensions = @('.bicep', '.cpp', '.cs', '.dart', '.fs', '.glsl', '.go', '.groovy', '.java', '.js', '.kt', '.ll', '.mm', '.php', '.rs', '.scala', '.ts') style = @{ prefix = "//" multi = $false } } 'hash' = @{ extensions = @('.cmake', '.coffee', '.jl', '.pl', '.ps1', '.py', '.r', '.rb', '.sh', '.yaml', '.yml', 'Makefile') style = @{ prefix = "#" multi = $false } } 'dash' = @{ extensions = @('.lua', '.sql') style = @{ prefix = "--" multi = $false } } 'percent' = @{ extensions = @('.matlab', '.tex') style = @{ prefix = "%" multi = $false } } 'semicolon' = @{ extensions = @('.el') style = @{ prefix = ";;" multi = $false } } 'xml' = @{ extensions = @('.html', '.md', '.xml') style = @{ prefix = "<!--" suffix = "-->" multi = $true } } 'c-style' = @{ extensions = @('.c', '.h') style = @{ prefix = "/*" middle = " *" suffix = " */" multi = $true } } 'haskell' = @{ extensions = @('.hs') style = @{ prefix = "{-" suffix = "-}" multi = $true } } 'ocaml' = @{ extensions = @('.ml') style = @{ prefix = "(*" suffix = "*)" multi = $true } } 'batch' = @{ extensions = @('.bat', '.cmd') style = @{ prefix = "::" multi = $false } } } # Create extension to style lookup for faster access $extensionStyles = @{} foreach ($styleName in $commentStyles.Keys) { $style = $commentStyles[$styleName] foreach ($ext in $style.extensions) { $extensionStyles[$ext] = $style.style } } function Get-FileExtension { param ( [string]$filePath ) if ($filePath -match "Makefile$") { return "Makefile" } # Special handling for Matlab files if ($filePath -match "\.m$") { # Check if it's a Matlab file by looking for Matlab-specific keywords $content = Get-Content $filePath -Raw if ($content -match "function\s+|classdef\s+|^\s*%") { return ".matlab" } # Otherwise assume it's an Objective-C file return ".mm" # Changed from .m to .mm for clarity } return [System.IO.Path]::GetExtension($filePath).ToLower() } function Get-CopyrightHeader { param ( [string]$filePath ) $ext = Get-FileExtension $filePath $style = $extensionStyles[$ext] if (-not $style) { Write-Warning "No comment style defined for extension: $ext" return $null } if ($style.multi) { if ($style.middle) { # C-style multi-line comment return @" $($style.prefix) $($style.middle) $copyrightText $($style.middle) $licenseText $($style.suffix) "@ } else { # HTML/XML-style or other multi-line comment return @" $($style.prefix) $copyrightText $($style.prefix) $licenseText $($style.suffix) "@ } } else { # Single-line comment style return @" $($style.prefix) $copyrightText $($style.prefix) $licenseText "@ } } Get-ChildItem -Path $PSScriptRoot\..\..\src, $PSScriptRoot\..\..\tests -Recurse -File | ForEach-Object { # Skip auto-generated files if ($_.FullName -like "*\obj\*" -or $_.FullName -like "*\bin\*") { Write-Host "Skipping generated file $($_.FullName)" return } $content = Get-Content $_.FullName -Raw if ([string]::IsNullOrEmpty($content)) { Write-Host "Skipping empty file $($_.FullName)" return } $header = Get-CopyrightHeader $_.FullName if (-not $header) { Write-Host "Skipping unsupported file type: $($_.FullName)" return } $ext = Get-FileExtension $_.FullName $style = $extensionStyles[$ext] # Check if file already has a copyright header $hasHeader = $false $lines = $content -split "`n" foreach ($line in $lines) { $trimmedLine = $line.TrimStart() if ($trimmedLine.Contains($copyrightText)) { $hasHeader = $true break } } if ($hasHeader -and -not $Force) { Write-Host "Copyright header already exists in $($_.FullName)" # Skip files that already have the header unless Force is specified return } if ($hasHeader -and $Force) { Write-Host "Forcing update of copyright header in $($_.FullName)" # Remove existing header while preserving shebang lines $newLines = @() $foundCopyright = $false $preserveShebang = $false $blankLineRemoved = $false for ($i = 0; $i -lt $lines.Count; $i++) { $line = $lines[$i] $trimmedLine = $line.TrimStart() # Always preserve shebang lines if ($trimmedLine -match '^#!' -and -not $preserveShebang) { $newLines += $line $preserveShebang = $true continue } # Look for copyright if ($trimmedLine.Contains($copyrightText)) { $foundCopyright = $true # Skip this line and next license line $i++ # Skip license line # Skip any blank lines after the header while (($i + 1) -lt $lines.Count -and [string]::IsNullOrWhiteSpace($lines[$i + 1].TrimStart())) { $i++ $blankLineRemoved = $true } continue } if (-not $foundCopyright) { $newLines += $line } else { # Only add the line if we've found the copyright and processed any trailing blanks $newLines += $line } } $content = $newLines -join "`n" } # Add the new header (which includes its own blank line) if ($lines[0] -match '^#!' -and -not $hasHeader) { $shebangLine = $lines[0] + "`n" $content = $content.Substring($lines[0].Length).TrimStart() $newContent = $shebangLine + $header + $content } else { $content = $content.TrimStart() $newContent = $header + $content } Set-Content -Path $_.FullName -Value $newContent -NoNewline Write-Host "Updated copyright header in $($_.FullName)" }