# Copyright (c) Microsoft Corporation # SPDX-License-Identifier: MIT $usage=$false; $quiet=$false; $verbose=$false; $whatif=$false; [System.Collections.ArrayList]$userExcludeDirs=@(); [System.Collections.ArrayList]$userIncludeExts=@(); [System.Collections.ArrayList]$excludeDirs=@(); [System.Collections.ArrayList]$includeExts=@(); [System.Collections.ArrayList]$userFiles=@(); ##============================================================================== ## ## Echo if verbose flag (ignores quiet flag) ## ##============================================================================== function log_verbose() { if ($verbose) { Write-Host "$args" } } ##============================================================================== ## ## Echo if whatif flag is specified but not quiet flag ## ##============================================================================== function log_whatif() { if ( $whatif -and -not $quiet) { Write-Host "$args" } } ##============================================================================== ## ## Process command-line options ## ## Note that in Powershell syntax, fallthrough does not work as such, ## instead one must compare against multiple values. For a discussion, see ## https://stackoverflow.com/questions/3493731/whats-the-powershell-syntax-for-multiple-values-in-a-switch-statement ## ##============================================================================== foreach ($opt in $args) { switch -regex ($opt) { { @("-h", "--help") -contains $_ } { $usage=$true; break; } { @("-q", "--quiet") -contains $_ } { $quiet=$true; break; } { @("-s", "--staged") -contains $_ } { $userFiles=@(git diff --cached --name-only --diff-filter=ACMR); break; } { @("-v", "--verbose") -contains $_ } { $verbose=$true; break; } { @("-w", "--whatif") -contains $_ } { $whatif=$true; break; } "--exclude-dirs=*" { $userExcludeDirs=($opt -split "=")[1]; break; } "--include-exts=*" { $userIncludeExts=($opt -split "=")[1]; break; } "--files=*" { $userFiles=($opt -split "=")[1]; break; } default { Write-Error "$PSCommandPath unknown option: $opt" exit 1 break; } } } ##============================================================================== ## ## Display help ## ##============================================================================== if ( $usage ) { $usageMessage = @' OVERVIEW: Formats all C/C++ source files based on the .clang-format rules $ format-code [-h] [-q] [-s] [-v] [-w] [--exclude-dirs="..."] [--include-exts="..."] [--files="..."] OPTIONS: -h, --help Print this help message. -q, --quiet Display only clang-format output and errors. -s, --staged Only format files which are staged to be committed. -v, --verbose Display verbose output. -w, --whatif Run the script without actually modifying the files and display the diff of expected changes, if any. --exclude-dirs Subdirectories to exclude. If unspecified, then ./external, ./packages and ./x64 are excluded. All subdirectories are relative to the current path. --include-exts File extensions to include for formatting. If unspecified, then *.h, *.hpp, *.c, *.cpp, *idl, and *.acf are included. --files Only run the script against the specified files from the current directory. EXAMPLES: To determine what lines of each file in the default configuration would be modified by format-code, you can run from the root folder: $ ./scripts/format-code -w To update only all .c and .cpp files in src/ except for src/tools/netsh, you can run from the src folder: src$ ../scripts/format-code --exclude-dirs="tools/netsh" \ --include-exts="c cpp" To run only against a specified set of comma separated files in the current directory: $ ./scripts/format-code -w --files="file1 file2" '@ Write-Host "$usageMessage" exit 0 } ##============================================================================== ## ## Determine parameters for finding files to format ## ##============================================================================== function get_find_args() { $defaultExcludeDirs=@( ".git", "external", "packages", "x64" ); $defaultIncludeExts=@( "h", "hpp", "c", "cpp", "idl", "acf" ) $findargs='get-childitem -Recurse -Name "*" -Path "." ' if ( !($userIncludeExts) ) { # not local as this is used in get_file_list() too $includeExts.AddRange($defaultIncludeExts) } else { log_verbose "Using user extension inclusions: $userIncludeExts" $includeExts.AddRange($userIncludeExts) } $findargs+=" -Include @( " foreach ($ext in $includeExts) { $findargs+=("'*."+"$ext'") if ($includeExts.IndexOf($ext) -lt $includeExts.count-1) { $findargs+=", " } } $findargs+=") " if ( !($userExcludeDirs) ) { $excludeDirs.AddRange($defaultExcludeDirs) } else { log_verbose "Using user directory exclusions: $userExcludeDirs" $excludeDirs.AddRange($userExcludeDirs) } $findargs+=" | where { " foreach ($dir in $excludeDirs) { $findargs+='$_ -notlike ' $findargs+= "'$dir"+"\*'" if ($excludeDirs.IndexOf($dir) -lt $excludeDirs.count-1) { $findargs+=" -and " } } $findargs+="} " return $findargs } function get_file_list() { if ( !($userFiles) ) { $file_list = Invoke-Expression($findargs) if ( $file_list.count -eq 0 ) { Write-Host "No files were found to format!" exit 1 } } else { log_verbose "Using user files: $userfiles" $file_list=@() foreach ( $file_name in $userfiles ) { $user_file_name = get-ChildItem -Path '.' -Name $file_name $file = New-Object System.IO.FileInfo($user_file_name) foreach ( $ext in $includeExts ) { if ( $file.Extension -eq ".$ext" ) { $file_list += $file_name log_verbose "Checking user file: $file_name" break; } } } } return $file_list } $global:cf="" ##============================================================================== ## ## Check for installed clang-format tool ## ##============================================================================== function check_clang-format() { # Windows does not have a clang-format-7 executable $required_cfver='11.0.1' try { $cfver=(( Invoke-Expression "clang-format --version" 2> $null ) -split " ")[2] } catch { Write-Host "clang-format not installed" return $false } $req_ver = $required_cfver -split '.' $cf_ver = $cfver -split '.' for ($i = 0; $i -lt 3; $i++) { if ( $cf_ver[$i] -gt $req_ver[$i]) { return $true } if ( $cf_ver[$i] -lt $req_ver[$i]) { Write-Host "Required version of clang-format is $required_cfver. Current version is $cfver" return $false } # Equal just keeps going } $global:cf="clang-format" return $true } ##============================================================================== ## ## Mainline: Call clang-format for each file to be formatted ## ##============================================================================== if (!(check_clang-format)) # getting the filelist takes a few seconds. If we cant format we may as well exit now. { exit -1 } $findargs = get_find_args; $filelist = get_file_list; $filecount=0 $changecount=0 $cfargs="$global:cf -style=file" if ( !$whatif ) { $cfargs="$cfargs -i" } foreach ( $file in $filelist ) { $filecount+=1; $cf="$cfargs $file" if ( $whatif ) { log_whatif "Formatting $file ..." ( Invoke-Expression ($cf) ) | Compare-Object (get-content $file) } else { if ( $verbose ) { log_verbose "Formatting $file ..." Invoke-Expression $cf } else { Invoke-Expression $cf > $null } } if ( $? ) { if ( $whatif ) { $changecount++ } } else { Write-Host "clang-format failed on file: $file." } } log_whatif "$filecount files processed, $changecount changed." # If files are being edited, this count is zero so we exit with success. exit $changecount