diff --git a/.vsts-ci.yml b/.vsts-ci.yml index 2b9851472bfe..1dee68b61e96 100644 --- a/.vsts-ci.yml +++ b/.vsts-ci.yml @@ -173,6 +173,7 @@ stages: _Test: -test - template: /eng/template-engine.yml + - template: /eng/dotnet-format/dotnet-format-integration.yml - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - template: /eng/common/templates/job/publish-build-assets.yml diff --git a/CODEOWNERS b/CODEOWNERS index 7b72f6089ed0..fb12e083587e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -92,3 +92,7 @@ /test/containerize.UnitTests @dotnet/sdk-container-builds-maintainers /test/Microsoft.NET.Build.Containers.IntegrationTests @dotnet/sdk-container-builds-maintainers /test/Microsoft.NET.Build.Containers.UnitTests @dotnet/sdk-container-builds-maintainers + +# dotnet-format +/src/BuiltInTools/dotnet-format @dotnet/roslyn-ide +/test/dotnet-format.Tests @dotnet/roslyn-ide \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index 74d52ab44430..0747ba9d407f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,8 +1,11 @@ + + + $(NoWarn);NU1507 + - @@ -10,18 +13,24 @@ - + + - + + + + + + @@ -45,6 +54,8 @@ + + @@ -74,6 +85,7 @@ + @@ -111,6 +123,7 @@ + @@ -121,6 +134,7 @@ + diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT new file mode 100644 index 000000000000..859bd1a6d9cd --- /dev/null +++ b/THIRD-PARTY-NOTICES.TXT @@ -0,0 +1,11 @@ +.NET Core uses third-party libraries or other resources that may be +distributed under licenses different than the .NET Core software. + +In the event that we accidentally failed to list a required notice, please +bring it to our attention. Post an issue or email us: + + dotnet@microsoft.com + +The attached notices are provided for information only. + +No notices are provided at this time. \ No newline at end of file diff --git a/build.cmd b/build.cmd index 6a89aa523ac8..39ba898bcc57 100644 --- a/build.cmd +++ b/build.cmd @@ -1,3 +1,3 @@ @echo off -powershell -NoLogo -NoProfile -ExecutionPolicy ByPass -Command "& """%~dp0eng\common\build.ps1""" -build -restore %*" +powershell -NoLogo -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0eng\common\build.ps1""" -restore -build -msbuildEngine dotnet %*" exit /b %ErrorLevel% diff --git a/documentation/format/docs/3rd-party-analyzers.md b/documentation/format/docs/3rd-party-analyzers.md new file mode 100644 index 000000000000..6bd606478a6a --- /dev/null +++ b/documentation/format/docs/3rd-party-analyzers.md @@ -0,0 +1,39 @@ +# 3rd Party Analyzers + +## How to add analyzers to a project + +3rd party analyzers are discovered from the `` specified in the workspace project files. + +*Example:* + +Add the StyleCop analyzer package to a simple console project file. + +```diff + + + + Exe + netcoreapp3.1 + + ++ ++ ++ + + +``` + +## How to configure analyzer severity + +The options specified in .editorconfig files are recognized by the pattern `dotnet_diagnostic..severity = `. `` represents the diagnostic ID matched by the compiler, case-insensitively, to be configured. `` must be one of the following: error, warn, info, hidden, suppress. Please read the [Code Analysis documentation](https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-options#severity-level) for more details. + +*Example:* + +Configure the StyleCop analyzer so that empty comments are errors. + +```ini +[*.{cs,vb}] + +# The C# comment does not contain any comment text. +dotnet_diagnostic.SA1120.severity = error +``` \ No newline at end of file diff --git a/documentation/format/docs/README.md b/documentation/format/docs/README.md new file mode 100644 index 000000000000..39a298411a5b --- /dev/null +++ b/documentation/format/docs/README.md @@ -0,0 +1,125 @@ +# Welcome to the dotnet-format docs! + +## .editorconfig options +- [Supported .editorconfig options](./Supported-.editorconfig-options.md) + +## CLI options + +### Specify a workspace (Required) + +A workspace path is needed when running dotnet-format. By default, the current folder will be used as the workspace path. The workspace path and type of workspace determines which code files are considered for formatting. + +- Solutions and Projects - By default dotnet-format will open the workspace path as a MSBuild solution or project. +- `--no-restore` - When formatting a solution or project the no restore option will stop dotnet-format from performing an implicit package restore. +- `--folder` - When the folder options is specified the workspace path will be treated as a folder of code files. + +*Example:* + +Format the code files used in the format solution. + +```console +dotnet format ./format.sln +``` + +Format the code files used in the dotnet-format project. + +```console +dotnet format ./src/dotnet-format.csproj +``` + +Format the code files from the `./src` folder. + +```console +dotnet format whitespace ./src --folder +``` + +### Whitespace formatting + +Whitespace formatting includes the core .editorconfig settings along with the placement of spaces and newlines. The whitespace formatter is run by default when not running analysis. When only performing whitespace formatting, an implicit restore is not perfomed. When you want to run analysis and fix formatting issues you must specify both. + +Whitespace formatting run by default along with code-style and 3rd party analysis. + +```console +dotnet format ./format.sln +``` + +Running the whitespace formatter alone. + +```console +dotnet format whitespace ./format.sln +``` + +### Running analysis + +#### CodeStyle analysis + +Running codestyle analysis requires the use of a MSBuild solution or project file as the workspace. By default an implicit restore on the solution or project is performed. Enforces the .NET [Language conventions](https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019) and [Naming conventions](https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions?view=vs-2019). + +- `dotnet format style --severity ` - Runs analysis and attempts to fix issues with severity equal or greater than specified. If severity is not specified then severity defaults to warning. + +*Example:* + +Code-style analysis is run by default along with whitespace formatting and 3rd party analysis. + +```console +dotnet format ./format.sln +``` + +Run code-style analysis alone against the dotnet-format project. + +```console +dotnet format style ./src/dotnet-format.csproj --severity error +``` + +Errors when used with the `--folder` option. Analysis requires a MSBuild solution or project. + +```console +dotnet format style ./src --folder +``` + +#### 3rd party analysis + +Running 3rd party analysis requires the use of a MSBuild solution or project file as the workspace. By default an implicit restore on the solution or project is performed. 3rd party analyzers are discovered from the `` specified in the workspace project files. + +- `dotnet format analyzers --severity ` - Runs analysis and attempts to fix issues with severity equal or greater than specified. If no severity is specified then this defaults to warning. + +#### Filter diagnostics to fix + +Typically when running codestyle or 3rd party analysis, all diagnostics of sufficient severity are reported and fixed. The `--diagnostics` option allows you to target a particular diagnostic or set of diagnostics of sufficient severity. + +- `--diagnostics ` - When used in conjunction with `style` or `analyzer` subcommands, allows you to apply targeted fixes for particular analyzers. + +*Example:* + +Run code-style analysis and fix unused using directive errors. + +```console +dotnet format style ./format.sln --diagnostics IDE0005 +``` + +### Filter files to format + +You can further narrow the list of files to be formatted by specifying a list of paths to include or exclude. When specifying folder paths the path must end with a directory separator. File globbing is supported. + +- `--include` - A list of relative file or folder paths to include in formatting. +- `--exclude` - A list of relative file or folder paths to exclude from formatting. + +*Example:* + +Other repos built as part of your project can be included using git submodules. These submodules likely contain their own .editorconfig files that are set as `root = true`. This makes it difficult to validate formatting for your project as formatting mistakes in submodules are treated as errors. + +The following command sets the repo folder as the workspace. It then includes the `src` and `tests` folders for formatting. The `submodule-a` folder is excluded from the formatting validation. + +```console +dotnet format whitespace --folder --include ./src/ ./tests/ --exclude ./src/submodule-a/ --verify-no-changes +``` + +### Logging and Reports + +- `--verbosity` - Set the verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] +- `--report` - Writes a json file to the given directory. Defaults to 'format-report.json' if no filename given. +- `--binarylog` - Writes a [binary log file](https://msbuildlog.com/) to help in diagnosing solution or project load errors. Defaults to 'format.binlog' if no filename given. + +### Validate formatting + +- `--verify-no-changes` - Formats files without saving changes to disk. Terminates with a non-zero exit code (`2`) if any files were formatted. diff --git a/documentation/format/docs/Supported-.editorconfig-options.md b/documentation/format/docs/Supported-.editorconfig-options.md new file mode 100644 index 000000000000..e4a184e99514 --- /dev/null +++ b/documentation/format/docs/Supported-.editorconfig-options.md @@ -0,0 +1,31 @@ +# Supported .editorconfig options +The dotnet-format global tool supports the core set of [EditorConfig options](https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties)* as well as the [.NET coding convention settings for EditorConfig](https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference?view=vs-2019). + +## Core options +- indent_style +- indent_size +- tab_width +- end_of_line +- charset +- insert_final_newline +- root + +[*] The options `trim_trailing_whitespace` and `max_line_length` are not supported. Currently insignificant whitespace is **always** removed by the formatter. + +## Removing unnecessary imports +In order to remove unnecessary imports the IDE0005 (unnecessary import) diagnostic id must be configured in your .editorconfig. When running dotnet-format specify a severity that includes the configured IDE0005 severity. + +*Example:* + +.editorconfig +```ini +root = true + +[*.{cs,vb}] +dotnet_diagnostic.IDE0005.severity = warning +``` + +command +```console +dotnet format ./format.sln --severity warn +``` \ No newline at end of file diff --git a/documentation/format/docs/integrations.md b/documentation/format/docs/integrations.md new file mode 100644 index 000000000000..3649e0603489 --- /dev/null +++ b/documentation/format/docs/integrations.md @@ -0,0 +1,90 @@ +# Integrations +Collection of advice how to auto check/format. Every sample expects dotnet format installed as local tool, unless otherwise noted. + +## Git pre-commit hook to reformat + +Create file `.git/hooks/pre-commit` with following contents: +```sh +#!/bin/sh +LC_ALL=C +# Select files to format +FILES=$(git diff --cached --name-only --diff-filter=ACM "*.cs" | sed 's| |\\ |g') +[ -z "$FILES" ] && exit 0 + +# Format all selected files +echo "$FILES" | cat | xargs | sed -e 's/ /,/g' | xargs dotnet format --include + +# Add back the modified files to staging +echo "$FILES" | xargs git add + +exit 0 + +``` + +These instructions originally authored by [randulakoralage82](https://medium.com/@randulakoralage82/format-your-net-code-with-git-hooks-a0dc33f68048). + + +## Check on PR in Azure Dev Ops + +Add following to your build file: + +```yaml +- task: UseDotNet@2 + displayName: 'Use .NET 6 sdk' + inputs: + packageType: 'sdk' + version: '6.0.x' + includePreviewVersions: true + +- task: DotNetCoreCLI@2 + displayName: 'dotnet-format' + inputs: + command: 'custom' + custom: 'format' + arguments: '--verify-no-changes' +``` + +These instructions originally authored by [leotsarev](https://github.com/joinrpg/joinrpg-net/). + + +## [pre-commit.com](https://pre-commit.com/) hook to reformat + +Add the following block to the `repos` section of your `.pre-commit-config.yaml` file: + +```yaml +- repo: https://github.com/dotnet/format + rev: "" # Specify a tag or sha here, or run "pre-commit autoupdate" + hooks: + - id: dotnet-format +``` +Note that this will compile and install dotnet format to an isolated environment, using the system installation of the dotnet CLI. See the [pre-commit.com documentation](https://pre-commit.com/#dotnet) for more details. The problem is that dotnet format is using *preview* SDK (even for 5.x versions), and you have to install preview SDK on your machine for compiling it. Another option is to use local feature of pre-commit, as follows: + +```yaml +- repo: local + hooks: + #Use dotnet format already installed on your machine + - id: dotnet-format + name: dotnet-format + language: system + entry: dotnet format --include + types_or: ["c#", "vb"] +``` + +These instructions originally authored by [rkm](https://github.com/rkm) & [leotsarev](https://github.com/joinrpg/joinrpg-net/). + + +## Rider reformat on save + +1. Open Settings -> Tools -> File Watchers +1. Press The “Plus Sign” to Add a Custom watcher +1. Set the name to i.e. “dotnet format on save” +1. FileType: C# +1. Scope: Open Files +1. Program: Write dotnet-format +1. Arguments: $SolutionPath$ --verbosity diagnostic --include $FileRelativePath$ +1. (Optionally) Append --fix-style warning to fix any style issues automatically on save. +1. (Optionally) Append --fix-analyzers warning to fix any analyzer warnings on save. +1. Disable all advanced option checkboxes. +1. All other values were left default + +These instructions originally authored by [Nils Henrik Hals](https://strepto.github.io/Pause/blog/dotnet-format-rider/). diff --git a/eng/SourceBuildPrebuiltBaseline.xml b/eng/SourceBuildPrebuiltBaseline.xml index a7cecae98a3b..6f69b678faaa 100644 --- a/eng/SourceBuildPrebuiltBaseline.xml +++ b/eng/SourceBuildPrebuiltBaseline.xml @@ -20,6 +20,10 @@ + + + + @@ -29,6 +33,7 @@ + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 34332a39d518..3d68bac615f7 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -87,16 +87,7 @@ 2c03643199368f07a3326709fc68abcbfc482a06 - - https://github.com/dotnet/format - 91f60316ebd9c75d6be8b7f9b7c201bab17240c9 - - - https://github.com/dotnet/format - 91f60316ebd9c75d6be8b7f9b7c201bab17240c9 - - https://github.com/dotnet/roslyn 2348a50bb566b39305c474793b43edb5635db6f4 @@ -374,6 +365,56 @@ https://github.com/dotnet/command-line-api 5ea97af07263ea3ef68a18557c8aa3f7e3200bda + + https://github.com/dotnet/command-line-api + e9ac4ff4293cf853f3d07eb9e747aef27f5be965 + + + + + https://github.com/dotnet/msbuild + d6990bcfaf520c0d215a194fad0617f7efad68b4 + + + + https://github.com/dotnet/msbuild + d6990bcfaf520c0d215a194fad0617f7efad68b4 + + + https://github.com/dotnet/msbuild + d6990bcfaf520c0d215a194fad0617f7efad68b4 + + + https://github.com/dotnet/msbuild + d6990bcfaf520c0d215a194fad0617f7efad68b4 + + + https://github.com/dotnet/msbuild + d6990bcfaf520c0d215a194fad0617f7efad68b4 + + + https://github.com/dotnet/msbuild + d6990bcfaf520c0d215a194fad0617f7efad68b4 + + + https://github.com/dotnet/roslyn + 744a0ae8691c5c463f63e9936b7d56592c0ed57c + + + + https://github.com/dotnet/symreader + 27e584661980ee6d82c419a2a471ae505b7d122e + + + https://github.com/dotnet/runtime + 9699f39112b2aea89a05a74199baf9049db85537 + + + https://github.com/dotnet/runtime + 9699f39112b2aea89a05a74199baf9049db85537 + https://github.com/dotnet/command-line-api diff --git a/eng/Versions.props b/eng/Versions.props index c40757e494ad..77c7c71c864c 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -38,7 +38,11 @@ 9.0.0-preview.3.24162.31 4.6.0 2.0.0-beta4.24126.1 + 0.4.0-alpha.24112.1 2.0.0-preview.1.24168.2 + 2.0.0 + 1.1.2-beta1.22216.1 + 10.3.0 3.2.2146 0.3.49-beta @@ -87,10 +91,6 @@ 9.0.0-preview.3.24162.31 9.0.0-preview.3.24162.31 - - - 9.0.515801 - 9.0.0-preview.24165.2 diff --git a/eng/dotnet-format/dotnet-format-integration.yml b/eng/dotnet-format/dotnet-format-integration.yml new file mode 100644 index 000000000000..4910ee594a22 --- /dev/null +++ b/eng/dotnet-format/dotnet-format-integration.yml @@ -0,0 +1,85 @@ +jobs: +- job: Formatting_Check + pool: + vmImage: 'windows-latest' + timeoutInMinutes: 60 + steps: + - script: .\restore.cmd + displayName: Restore dependencies + - script: | + .\artifacts\sdk-build-env.bat + dotnet run --project .\src\BuiltInTools\dotnet-format\dotnet-format.csproj -c Release -- @eng\dotnet-format\validate.rsp + displayName: Run dotnet-format + - task: PublishBuildArtifacts@1 + displayName: Publish Logs + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)' + ArtifactName: 'dotnet-format formatting Check' + continueOnError: true + condition: not(succeeded()) + +- job: dotnet_format_integration_tests + pool: + vmImage: 'windows-latest' + strategy: + maxParallel: 8 + matrix: + roslyn: + _repo: "https://github.com/dotnet/roslyn" + _repoName: "dotnet/roslyn" + _targetSolution: "Compilers.slnf" + _branchName: "main" + _sha: "a3bb37003aeccad012a6e7dd220977599e8b8e65" + _useParentSdk: 0 + sdk: + _repo: "https://github.com/dotnet/sdk" + _repoName: "dotnet/sdk" + _targetSolution: "sdk.sln" + _branchName: "main" + _sha: "be25db95c376bffd508a023399ddd34392fe6458" + _useParentSdk: 0 + project-system: + _repo: "https://github.com/dotnet/project-system" + _repoName: "dotnet/project-system" + _targetSolution: "ProjectSystem.sln" + _branchName: "main" + _sha: "e660d54d6b3198751bd0502fe270e1657f32a913" + _useParentSdk: 1 + msbuild: + _repo: "https://github.com/dotnet/msbuild" + _repoName: "dotnet/msbuild" + _targetSolution: "MSBuild.sln" + _branchName: "main" + _sha: "f4fa6bde775a3f7cbb2bb90a349ee5fc759114f3" + _useParentSdk: 0 + aspnetcore: + _repo: "https://github.com/dotnet/aspnetcore" + _repoName: "dotnet/aspnetcore" + _targetSolution: "AspNetCore.sln" + _branchName: "main" + _sha: "d765d7ba4871a8c2cb38d4134553d3be9a7370d7" + _useParentSdk: 0 + efcore: + _repo: "https://github.com/dotnet/efcore" + _repoName: "dotnet/efcore" + _targetSolution: "All.sln" + _branchName: "main" + _sha: "1b2ff365399ab6736a9ea4c98ab1b60acda5d917" + _useParentSdk: 0 + razor-tooling: + _repo: "https://github.com/dotnet/razor" + _repoName: "dotnet/razor" + _targetSolution: "Razor.sln" + _branchName: "main" + _sha: "ecb4b595e3322a18c240f50a763868540f51eaaa" + _useParentSdk: 0 + timeoutInMinutes: 60 + steps: + - script: eng\dotnet-format\integration-test.cmd -repo '$(_repo)' -branchName '$(_branchName)' -sha '$(_sha)' -targetSolution '$(_targetSolution)' -useParentSdk $(_useParentSdk) -testPath '$(Agent.TempDirectory)\temp' -stage 'prepare' + displayName: Prepare $(_repoName) for formatting + + - script: eng\dotnet-format\integration-test.cmd -repo '$(_repo)' -branchName '$(_branchName)' -sha '$(_sha)' -targetSolution '$(_targetSolution)' -useParentSdk $(_useParentSdk) -testPath '$(Agent.TempDirectory)\temp' -stage 'format-workspace' + displayName: Run dotnet-format on $(_repoName) $(_targetSolution) + + - script: eng\dotnet-format\integration-test.cmd -repo '$(_repo)' -branchName '$(_branchName)' -sha '$(_sha)' -targetSolution '$(_targetSolution)' -useParentSdk $(_useParentSdk) -testPath '$(Agent.TempDirectory)\temp' -stage 'format-folder' + displayName: Run dotnet-format on $(_repoName) repo folder diff --git a/eng/dotnet-format/format-verifier.ps1 b/eng/dotnet-format/format-verifier.ps1 new file mode 100644 index 000000000000..083e1c72085f --- /dev/null +++ b/eng/dotnet-format/format-verifier.ps1 @@ -0,0 +1,129 @@ +[CmdletBinding(PositionalBinding = $false)] +Param( + [string]$repo, + [string]$sha, + [string]$branchName, + [string]$targetSolution, + [bool]$useParentSdk, + [string]$testPath, + [string]$stage # Valid values are "prepare", "format-workspace", "format-folder" +) + +if ($stage -eq "prepare") { + Write-Output "$(Get-Date) - Building dotnet-format." + .\build.cmd -c Release +} + +$currentLocation = Get-Location +$dotnetPath = Join-Path $currentLocation ".dotnet" +$parentDotNetPath = Join-Path $dotnetPath "dotnet.exe" + +if (!(Test-Path $testPath)) { + New-Item -ItemType Directory -Force -Path $testPath | Out-Null +} + +try { + $repoName = $repo.Substring(19) + $folderName = $repoName.Split("/")[1] + $repoPath = Join-Path $testPath $folderName + $dllPath = Get-ChildItem -Path "$currentLocation/artifacts/bin/dotnet-format/Release/" -Include dotnet-format.dll -Recurse + + if (!(Test-Path $repoPath)) { + New-Item -ItemType Directory -Force -Path $repoPath | Out-Null + } + + Set-Location $repoPath + + if ($stage -eq "prepare") { + Write-Output "$(Get-Date) - Cloning $repoName." + git.exe init + git.exe remote add origin $repo + git.exe fetch --progress --no-tags --depth=1 origin $sha + git.exe checkout $sha + } + + # We invoke build.ps1 ourselves because running `restore.cmd` invokes the build.ps1 + # in a child process which means added .NET Core SDKs aren't visible to this process. + if (Test-Path '.\eng\Build.ps1') { + Write-Output "$(Get-Date) - Running Build.ps1 -restore" + .\eng\Build.ps1 -restore + } + elseif (Test-Path '.\eng\common\Build.ps1') { + Write-Output "$(Get-Date) - Running Build.ps1 -restore" + .\eng\common\Build.ps1 -restore + } + + if ($stage -eq "prepare" -or $stage -eq "format-workspace") { + Write-Output "$(Get-Date) - Finding solutions." + $solutions = Get-ChildItem -Filter *.sln -Recurse -Depth 2 | Select-Object -ExpandProperty FullName | Where-Object { $_ -match '.sln$' } + + foreach ($solution in $solutions) { + $solutionPath = Split-Path $solution + $solutionFile = Split-Path $solution -leaf + + if ($solutionFile -ne $targetSolution) { + continue + } + + Set-Location $solutionPath + + if ($stage -eq "prepare") { + Write-Output "$(Get-Date) - $solutionFile - Restoring" + + # Restore the solution either with the repo local sdk or optionally with the dotnet/format SDK. + if ($useParentSdk) { + & $parentDotNetPath restore $solution --configfile nuget.config + } else { + dotnet.exe restore $solution + } + } + + if ($stage -eq "format-workspace") { + Write-Output "$(Get-Date) - $solutionFile - Formatting Workspace" + $output = & $parentDotNetPath "$dllPath" $solution --no-restore -v diag --verify-no-changes | Out-String + Write-Output $output.TrimEnd() + + # Ignore CheckFailedExitCode since we don't expect these repos to be properly formatted. + if ($LastExitCode -ne 0 -and $LastExitCode -ne 2) { + Write-Output "$(Get-Date) - Formatting failed with error code $LastExitCode." + exit -1 + } + + if (($output -notmatch "(?m)Formatted \d+ of (\d+) files") -or ($Matches[1] -eq "0")) { + Write-Output "$(Get-Date) - No files found for solution." + # The dotnet/sdk has a toolset solution with no files. + # exit -1 + } + } + + Write-Output "$(Get-Date) - $solutionFile - Complete" + } + } + + if ($stage -eq "format-folder") { + Write-Output "$(Get-Date) - $folderName - Formatting Folder" + $output = & $parentDotNetPath "$dllPath" whitespace $repoPath --folder -v diag --verify-no-changes | Out-String + Write-Output $output.TrimEnd() + + # Ignore CheckFailedExitCode since we don't expect these repos to be properly formatted. + if ($LastExitCode -ne 0 -and $LastExitCode -ne 2) { + Write-Output "$(Get-Date) - Formatting failed with error code $LastExitCode." + exit -1 + } + + if (($output -notmatch "(?m)Formatted \d+ of (\d+) files") -or ($Matches[1] -eq "0")) { + Write-Output "$(Get-Date) - No files found for solution." + exit -1 + } + + Write-Output "$(Get-Date) - $folderName - Complete" + } + + exit 0 +} +catch { + exit -1 +} +finally { + Set-Location $currentLocation +} diff --git a/eng/dotnet-format/integration-test.cmd b/eng/dotnet-format/integration-test.cmd new file mode 100644 index 000000000000..bb23395fc926 --- /dev/null +++ b/eng/dotnet-format/integration-test.cmd @@ -0,0 +1,3 @@ +@echo off +powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0format-verifier.ps1""" %*" +exit /b %ErrorLevel% \ No newline at end of file diff --git a/eng/dotnet-format/validate.rsp b/eng/dotnet-format/validate.rsp new file mode 100644 index 000000000000..b42437137cd5 --- /dev/null +++ b/eng/dotnet-format/validate.rsp @@ -0,0 +1,8 @@ +./src/BuiltInTools/dotnet-format.slnf +--exclude +./tests/projects/ +--verify-no-changes +--report +./artifacts/log/ +-v +diag diff --git a/restore.cmd b/restore.cmd index 5b27e5e40790..4c12755f6091 100644 --- a/restore.cmd +++ b/restore.cmd @@ -1,3 +1,3 @@ @echo off -powershell -NoLogo -NoProfile -ExecutionPolicy ByPass -Command "& """%~dp0eng\common\build.ps1""" -restore %*" +powershell -NoLogo -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0eng\common\build.ps1""" -restore -msbuildEngine dotnet %*" exit /b %ErrorLevel% diff --git a/sdk.sln b/sdk.sln index 48deada62416..6088dfb1679b 100644 --- a/sdk.sln +++ b/sdk.sln @@ -483,6 +483,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ApiCompat", "ApiCompat", "{ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GenAPI", "GenAPI", "{85A01ACB-CC90-45EF-8F6C-AFC2B9F31126}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-format", "src\BuiltInTools\dotnet-format\dotnet-format.csproj", "{7382A1CB-AA9A-4136-A548-17D7A67C6B0C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-format.UnitTests", "test\dotnet-format.Tests\tests\dotnet-format.UnitTests.csproj", "{D7495CE7-64E5-4715-9304-799A41EC1D71}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -901,6 +905,14 @@ Global {FEA8B7B5-901B-4A3A-948F-7E5F54F09FF5}.Debug|Any CPU.Build.0 = Debug|Any CPU {FEA8B7B5-901B-4A3A-948F-7E5F54F09FF5}.Release|Any CPU.ActiveCfg = Release|Any CPU {FEA8B7B5-901B-4A3A-948F-7E5F54F09FF5}.Release|Any CPU.Build.0 = Release|Any CPU + {7382A1CB-AA9A-4136-A548-17D7A67C6B0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7382A1CB-AA9A-4136-A548-17D7A67C6B0C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7382A1CB-AA9A-4136-A548-17D7A67C6B0C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7382A1CB-AA9A-4136-A548-17D7A67C6B0C}.Release|Any CPU.Build.0 = Release|Any CPU + {D7495CE7-64E5-4715-9304-799A41EC1D71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7495CE7-64E5-4715-9304-799A41EC1D71}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7495CE7-64E5-4715-9304-799A41EC1D71}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7495CE7-64E5-4715-9304-799A41EC1D71}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1067,6 +1079,8 @@ Global {3AD322BF-405B-4A53-9858-51CF66E8509F} = {580D1AE7-AA8F-4912-8B76-105594E00B3B} {F6D8919A-CC7F-4D0D-9C54-BFD2F2380FB5} = {3AD322BF-405B-4A53-9858-51CF66E8509F} {85A01ACB-CC90-45EF-8F6C-AFC2B9F31126} = {3AD322BF-405B-4A53-9858-51CF66E8509F} + {7382A1CB-AA9A-4136-A548-17D7A67C6B0C} = {71A9F549-0EB6-41F9-BC16-4A6C5007FC91} + {D7495CE7-64E5-4715-9304-799A41EC1D71} = {580D1AE7-AA8F-4912-8B76-105594E00B3B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FB8F26CE-4DE6-433F-B32A-79183020BBD6} diff --git a/src/Assets/dotnet-format/Directory.Build.props b/src/Assets/dotnet-format/Directory.Build.props new file mode 100644 index 000000000000..2f00fb236f84 --- /dev/null +++ b/src/Assets/dotnet-format/Directory.Build.props @@ -0,0 +1,10 @@ + + + + + false + false + + + diff --git a/src/Assets/dotnet-format/for_analyzer_formatter/analyzer_project/NuGet.config b/src/Assets/dotnet-format/for_analyzer_formatter/analyzer_project/NuGet.config new file mode 100644 index 000000000000..313d62457ff3 --- /dev/null +++ b/src/Assets/dotnet-format/for_analyzer_formatter/analyzer_project/NuGet.config @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/Assets/dotnet-format/for_analyzer_formatter/analyzer_project/analyzer_project.csproj b/src/Assets/dotnet-format/for_analyzer_formatter/analyzer_project/analyzer_project.csproj new file mode 100644 index 000000000000..82323fd062b9 --- /dev/null +++ b/src/Assets/dotnet-format/for_analyzer_formatter/analyzer_project/analyzer_project.csproj @@ -0,0 +1,18 @@ + + + + netstandard2.0 + + + + + runtime; build; native; contentfiles; analyzers + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/.editorconfig b/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/.editorconfig new file mode 100644 index 000000000000..d60a5519847a --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/.editorconfig @@ -0,0 +1,220 @@ +# EditorConfig is awesome:http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Don't use tabs for indentation. +[*] +indent_style = space +# (Please don't specify an indent_size here; that has too many unintended consequences.) + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8-bom + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# Xml config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# JSON files +[*.json] +indent_size = 2 + +[*.{sh}] +end_of_line = lf +indent_size = 2 + +# Dotnet code style settings: +[*.{cs,vb}] +# Sort using and Import directives with System.* appearing first +dotnet_sort_system_directives_first = true +# Avoid "this." and "Me." if not necessary +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning + +# Use language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning + +# Suggest more modern language features when available +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion + +# Non-private static fields are PascalCase +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style + +dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field +dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected +dotnet_naming_symbols.non_private_static_fields.required_modifiers = static + +dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case + +# Constants are PascalCase +dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants +dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style + +dotnet_naming_symbols.constants.applicable_kinds = field, local +dotnet_naming_symbols.constants.required_modifiers = const + +dotnet_naming_style.constant_style.capitalization = pascal_case + +# Static fields are camelCase and start with s_ +dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields +dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style + +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static + +dotnet_naming_style.static_field_style.capitalization = camel_case +dotnet_naming_style.static_field_style.required_prefix = s_ + +# Instance fields are camelCase and start with _ +dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields +dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style + +dotnet_naming_symbols.instance_fields.applicable_kinds = field + +dotnet_naming_style.instance_field_style.capitalization = camel_case +dotnet_naming_style.instance_field_style.required_prefix = _ + +# Locals and parameters are camelCase +dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion +dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters +dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style + +dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local + +dotnet_naming_style.camel_case_style.capitalization = camel_case + +# Local functions are PascalCase +dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions +dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style + +dotnet_naming_symbols.local_functions.applicable_kinds = local_function + +dotnet_naming_style.local_function_style.capitalization = pascal_case + +# By default, name items with PascalCase +dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members +dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style + +dotnet_naming_symbols.all_members.applicable_kinds = * + +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# CSharp code style settings: +[*.cs] +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left + +# Prefer "var" everywhere +dotnet_diagnostic.IDE0007.severity = warning +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = true:warning + +# Prefer method-like constructs to have a block body +csharp_style_expression_bodied_methods = false:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = false:none + +# Prefer property-like constructs to have an expression-body +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_accessors = true:warning + +# Suggest more modern language features when available +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_throw_expression = true:warning +csharp_style_conditional_delegate_call = true:suggestion + +# Newline settings +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Spacing +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_around_binary_operators = before_and_after +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false + +# Blocks are allowed +csharp_prefer_braces = true:silent +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +# IDE0035: Remove unreachable code +dotnet_diagnostic.IDE0035.severity = warning + +# IDE0036: Order modifiers +dotnet_diagnostic.IDE0036.severity = warning + +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0040.severity = warning + +# IDE0043: Format string contains invalid placeholder +dotnet_diagnostic.IDE0043.severity = warning + +# IDE0044: Make field readonly +dotnet_diagnostic.IDE0044.severity = warning + +# CONSIDER: Are IDE0051 and IDE0052 too noisy to be warnings for IDE editing scenarios? Should they be made build-only warnings? +# IDE0051: Remove unused private member +dotnet_diagnostic.IDE0051.severity = warning + +# IDE0052: Remove unread private member +dotnet_diagnostic.IDE0052.severity = warning + +# IDE0059: Unnecessary assignment to a value +dotnet_diagnostic.IDE0059.severity = warning + +# IDE0060: Remove unused parameter +dotnet_diagnostic.IDE0060.severity = warning + +# IDE0005: Using directive is unnecessary +dotnet_diagnostic.IDE0005.severity = warning + +# CA1027: Mark enums with FlagsAttribute +dotnet_diagnostic.CA1027.severity = error + +# CA1028: Enum storage should be Int32 +dotnet_diagnostic.CA1028.severity = error + +dotnet_diagnostic.RS0016.severity = warning \ No newline at end of file diff --git a/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/analyzers_solution.sln b/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/analyzers_solution.sln new file mode 100644 index 000000000000..50af93db9cef --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/analyzers_solution.sln @@ -0,0 +1,30 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28621.142 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "console_project", ".\console_project\console_project.csproj", "{EE13F2F8-3254-46A8-8A49-02C428EDE60A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "library", ".\library\library.csproj", "{EE13F2F8-3254-46A8-8A49-02C428EDE60B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EE13F2F8-3254-46A8-8A49-02C428EDE60A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60A}.Release|Any CPU.Build.0 = Release|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C0AFE45C-FD8E-4E3B-9300-8C067DE00C01} + EndGlobalSection +EndGlobal diff --git a/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/console_project/DaysEnumNeedsFlags.cs b/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/console_project/DaysEnumNeedsFlags.cs new file mode 100644 index 000000000000..8240cf30749c --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/console_project/DaysEnumNeedsFlags.cs @@ -0,0 +1,15 @@ +using System; + +namespace console_project +{ + public enum DaysEnumNeedsFlags : uint + { + None = 0, + Monday = 1, + Tuesday = 2, + Wednesday = 4, + Thursday = 8, + Friday = 16, + All = Monday | Tuesday | Wednesday | Thursday | Friday + } +} diff --git a/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/console_project/Program.cs b/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/console_project/Program.cs new file mode 100644 index 000000000000..7c16b6d83e11 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/console_project/Program.cs @@ -0,0 +1,14 @@ +using System; +using library; + +namespace codestyle_project +{ + class Program + { + static void Main(string[] args) + { + Speaker speaker = new Speaker(); + Console.WriteLine(speaker.SayHello("World")); + } + } +} diff --git a/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/console_project/console_project.csproj b/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/console_project/console_project.csproj new file mode 100644 index 000000000000..5fe28f3cdd73 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/console_project/console_project.csproj @@ -0,0 +1,18 @@ + + + + Exe + netcoreapp3.1 + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + diff --git a/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/library/PublicAPI.Shipped.txt b/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/library/PublicAPI.Shipped.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/library/PublicAPI.Unshipped.txt b/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/library/PublicAPI.Unshipped.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/library/Speaker.cs b/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/library/Speaker.cs new file mode 100644 index 000000000000..16e4496bd344 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/library/Speaker.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace library +{ + public class Speaker + { + string lastName; + + public string SayHello(string name) + { + lastName = name; + return $"Hello {this.lastName}."; + } + } +} diff --git a/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/library/library.csproj b/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/library/library.csproj new file mode 100644 index 000000000000..b1e95b303ed0 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/analyzers_solution/library/library.csproj @@ -0,0 +1,14 @@ + + + + netstandard2.0 + + + + + all + runtime; build; native; contentfiles; analyzers + + + + diff --git a/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/.editorconfig b/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/.editorconfig new file mode 100644 index 000000000000..a2441517eef4 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/.editorconfig @@ -0,0 +1,212 @@ +# EditorConfig is awesome:http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Don't use tabs for indentation. +[*] +indent_style = space +# (Please don't specify an indent_size here; that has too many unintended consequences.) + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8-bom + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# Xml config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# JSON files +[*.json] +indent_size = 2 + +[*.{sh}] +end_of_line = lf +indent_size = 2 + +# Dotnet code style settings: +[*.{cs,vb}] +# Sort using and Import directives with System.* appearing first +dotnet_sort_system_directives_first = true +# Avoid "this." and "Me." if not necessary +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning + +# Use language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning + +# Suggest more modern language features when available +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion + +# Non-private static fields are PascalCase +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style + +dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field +dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected +dotnet_naming_symbols.non_private_static_fields.required_modifiers = static + +dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case + +# Constants are PascalCase +dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants +dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style + +dotnet_naming_symbols.constants.applicable_kinds = field, local +dotnet_naming_symbols.constants.required_modifiers = const + +dotnet_naming_style.constant_style.capitalization = pascal_case + +# Static fields are camelCase and start with s_ +dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields +dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style + +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static + +dotnet_naming_style.static_field_style.capitalization = camel_case +dotnet_naming_style.static_field_style.required_prefix = s_ + +# Instance fields are camelCase and start with _ +dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields +dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style + +dotnet_naming_symbols.instance_fields.applicable_kinds = field + +dotnet_naming_style.instance_field_style.capitalization = camel_case +dotnet_naming_style.instance_field_style.required_prefix = _ + +# Locals and parameters are camelCase +dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion +dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters +dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style + +dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local + +dotnet_naming_style.camel_case_style.capitalization = camel_case + +# Local functions are PascalCase +dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions +dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style + +dotnet_naming_symbols.local_functions.applicable_kinds = local_function + +dotnet_naming_style.local_function_style.capitalization = pascal_case + +# By default, name items with PascalCase +dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members +dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style + +dotnet_naming_symbols.all_members.applicable_kinds = * + +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# CSharp code style settings: +[*.cs] +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left + +# Prefer "var" everywhere +dotnet_diagnostic.IDE0007.severity = warning +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = true:warning + +# Prefer method-like constructs to have a block body +csharp_style_expression_bodied_methods = false:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = false:none + +# Prefer property-like constructs to have an expression-body +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_accessors = true:warning + +# Suggest more modern language features when available +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_throw_expression = true:warning +csharp_style_conditional_delegate_call = true:suggestion + +# Newline settings +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Spacing +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_around_binary_operators = before_and_after +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false + +# Blocks are allowed +csharp_prefer_braces = true:silent +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +# IDE0035: Remove unreachable code +dotnet_diagnostic.IDE0035.severity = warning + +# IDE0036: Order modifiers +dotnet_diagnostic.IDE0036.severity = warning + +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0040.severity = warning + +# IDE0043: Format string contains invalid placeholder +dotnet_diagnostic.IDE0043.severity = warning + +# IDE0044: Make field readonly +dotnet_diagnostic.IDE0044.severity = warning + +# CONSIDER: Are IDE0051 and IDE0052 too noisy to be warnings for IDE editing scenarios? Should they be made build-only warnings? +# IDE0051: Remove unused private member +dotnet_diagnostic.IDE0051.severity = warning + +# IDE0052: Remove unread private member +dotnet_diagnostic.IDE0052.severity = warning + +# IDE0059: Unnecessary assignment to a value +dotnet_diagnostic.IDE0059.severity = warning + +# IDE0060: Remove unused parameter +dotnet_diagnostic.IDE0060.severity = warning + +# IDE0005: Using directive is unnecessary +dotnet_diagnostic.IDE0005.severity = warning diff --git a/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/codestyle_solution.sln b/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/codestyle_solution.sln new file mode 100644 index 000000000000..50af93db9cef --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/codestyle_solution.sln @@ -0,0 +1,30 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28621.142 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "console_project", ".\console_project\console_project.csproj", "{EE13F2F8-3254-46A8-8A49-02C428EDE60A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "library", ".\library\library.csproj", "{EE13F2F8-3254-46A8-8A49-02C428EDE60B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EE13F2F8-3254-46A8-8A49-02C428EDE60A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60A}.Release|Any CPU.Build.0 = Release|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C0AFE45C-FD8E-4E3B-9300-8C067DE00C01} + EndGlobalSection +EndGlobal diff --git a/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/codestyle_solution_filter.slnf b/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/codestyle_solution_filter.slnf new file mode 100644 index 000000000000..192ac4a850d5 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/codestyle_solution_filter.slnf @@ -0,0 +1,8 @@ +{ + "solution": { + "path": "codestyle_solution.sln", + "projects": [ + ".\\library\\library.csproj" + ] + } +} \ No newline at end of file diff --git a/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/console_project/Program.cs b/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/console_project/Program.cs new file mode 100644 index 000000000000..7c16b6d83e11 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/console_project/Program.cs @@ -0,0 +1,14 @@ +using System; +using library; + +namespace codestyle_project +{ + class Program + { + static void Main(string[] args) + { + Speaker speaker = new Speaker(); + Console.WriteLine(speaker.SayHello("World")); + } + } +} diff --git a/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/console_project/console_project.csproj b/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/console_project/console_project.csproj new file mode 100644 index 000000000000..b5fd59fc927b --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/console_project/console_project.csproj @@ -0,0 +1,11 @@ + + + + Exe + netcoreapp3.1 + + + + + + diff --git a/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/library/Speaker.cs b/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/library/Speaker.cs new file mode 100644 index 000000000000..16e4496bd344 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/library/Speaker.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace library +{ + public class Speaker + { + string lastName; + + public string SayHello(string name) + { + lastName = name; + return $"Hello {this.lastName}."; + } + } +} diff --git a/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/library/library.csproj b/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/library/library.csproj new file mode 100644 index 000000000000..9f5c4f4abb61 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/codestyle_solution/library/library.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + diff --git a/src/Assets/dotnet-format/for_code_formatter/formatted_project/.editorconfig b/src/Assets/dotnet-format/for_code_formatter/formatted_project/.editorconfig new file mode 100644 index 000000000000..5ba03353dfe7 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/formatted_project/.editorconfig @@ -0,0 +1,146 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 + +# New line preferences +# end_of_line = crlf +insert_final_newline = false + +#### .NET Coding Conventions #### + +# this. and Me. preferences +dotnet_style_qualification_for_event = false:silent +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_property = false:silent + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent + +# Expression-level preferences +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_throw_expression = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion + +# Field preferences +dotnet_style_readonly_field = true:suggestion + +# Parameter preferences +dotnet_code_quality_unused_parameters = all:suggestion + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false:silent +csharp_style_var_for_built_in_types = false:silent +csharp_style_var_when_type_is_apparent = false:silent + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async + +# Code-block preferences +csharp_prefer_braces = true:silent + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + diff --git a/src/Assets/dotnet-format/for_code_formatter/formatted_project/Program.cs b/src/Assets/dotnet-format/for_code_formatter/formatted_project/Program.cs new file mode 100644 index 000000000000..8fc2d56cfcc3 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/formatted_project/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace for_code_formatter +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} \ No newline at end of file diff --git a/src/Assets/dotnet-format/for_code_formatter/formatted_project/formatted_project.csproj b/src/Assets/dotnet-format/for_code_formatter/formatted_project/formatted_project.csproj new file mode 100644 index 000000000000..c73e0d1692ab --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/formatted_project/formatted_project.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp3.1 + + + diff --git a/src/Assets/dotnet-format/for_code_formatter/formatted_solution/formatted_solution.sln b/src/Assets/dotnet-format/for_code_formatter/formatted_solution/formatted_solution.sln new file mode 100644 index 000000000000..15135c46c237 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/formatted_solution/formatted_solution.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28621.142 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "formatted_project", "..\formatted_project\formatted_project.csproj", "{EE13F2F8-3254-46A8-8A49-02C428EDE60A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EE13F2F8-3254-46A8-8A49-02C428EDE60A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C0AFE45C-FD8E-4E3B-9300-8C067DE00C01} + EndGlobalSection +EndGlobal diff --git a/src/Assets/dotnet-format/for_code_formatter/fsharp_project/Program.fs b/src/Assets/dotnet-format/for_code_formatter/fsharp_project/Program.fs new file mode 100644 index 000000000000..358ad0edc6d0 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/fsharp_project/Program.fs @@ -0,0 +1,8 @@ +// Learn more about F# at http://fsharp.org + +open System + +[] +let main argv = + printfn "Hello World from F#!" + 0 // return an integer exit code diff --git a/src/Assets/dotnet-format/for_code_formatter/fsharp_project/fsharp_project.fsproj b/src/Assets/dotnet-format/for_code_formatter/fsharp_project/fsharp_project.fsproj new file mode 100644 index 000000000000..7486254df818 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/fsharp_project/fsharp_project.fsproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp3.1 + + + + + + + \ No newline at end of file diff --git a/src/Assets/dotnet-format/for_code_formatter/generated_project/.editorconfig b/src/Assets/dotnet-format/for_code_formatter/generated_project/.editorconfig new file mode 100644 index 000000000000..3eab6df6265e --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/generated_project/.editorconfig @@ -0,0 +1,149 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.cs] + +## All files should be considered generated code. +generated_code = true + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 2 +indent_style = space +tab_width = 2 + +# New line preferences +# end_of_line = crlf +insert_final_newline = false + +#### .NET Coding Conventions #### + +# this. and Me. preferences +dotnet_style_qualification_for_event = false:silent +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_property = false:silent + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent + +# Expression-level preferences +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_throw_expression = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion + +# Field preferences +dotnet_style_readonly_field = true:suggestion + +# Parameter preferences +dotnet_code_quality_unused_parameters = all:suggestion + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false:silent +csharp_style_var_for_built_in_types = false:silent +csharp_style_var_when_type_is_apparent = false:silent + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async + +# Code-block preferences +csharp_prefer_braces = true:silent + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + diff --git a/src/Assets/dotnet-format/for_code_formatter/generated_project/Program.cs b/src/Assets/dotnet-format/for_code_formatter/generated_project/Program.cs new file mode 100644 index 000000000000..e4cfa76c0046 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/generated_project/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace for_code_formatter +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/src/Assets/dotnet-format/for_code_formatter/generated_project/generated_project.csproj b/src/Assets/dotnet-format/for_code_formatter/generated_project/generated_project.csproj new file mode 100644 index 000000000000..c73e0d1692ab --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/generated_project/generated_project.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp3.1 + + + diff --git a/src/Assets/dotnet-format/for_code_formatter/generator_solution/.editorconfig b/src/Assets/dotnet-format/for_code_formatter/generator_solution/.editorconfig new file mode 100644 index 000000000000..d60a5519847a --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/generator_solution/.editorconfig @@ -0,0 +1,220 @@ +# EditorConfig is awesome:http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Don't use tabs for indentation. +[*] +indent_style = space +# (Please don't specify an indent_size here; that has too many unintended consequences.) + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8-bom + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# Xml config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# JSON files +[*.json] +indent_size = 2 + +[*.{sh}] +end_of_line = lf +indent_size = 2 + +# Dotnet code style settings: +[*.{cs,vb}] +# Sort using and Import directives with System.* appearing first +dotnet_sort_system_directives_first = true +# Avoid "this." and "Me." if not necessary +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning + +# Use language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning + +# Suggest more modern language features when available +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion + +# Non-private static fields are PascalCase +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style + +dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field +dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected +dotnet_naming_symbols.non_private_static_fields.required_modifiers = static + +dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case + +# Constants are PascalCase +dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants +dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style + +dotnet_naming_symbols.constants.applicable_kinds = field, local +dotnet_naming_symbols.constants.required_modifiers = const + +dotnet_naming_style.constant_style.capitalization = pascal_case + +# Static fields are camelCase and start with s_ +dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields +dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style + +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static + +dotnet_naming_style.static_field_style.capitalization = camel_case +dotnet_naming_style.static_field_style.required_prefix = s_ + +# Instance fields are camelCase and start with _ +dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields +dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style + +dotnet_naming_symbols.instance_fields.applicable_kinds = field + +dotnet_naming_style.instance_field_style.capitalization = camel_case +dotnet_naming_style.instance_field_style.required_prefix = _ + +# Locals and parameters are camelCase +dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion +dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters +dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style + +dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local + +dotnet_naming_style.camel_case_style.capitalization = camel_case + +# Local functions are PascalCase +dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions +dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style + +dotnet_naming_symbols.local_functions.applicable_kinds = local_function + +dotnet_naming_style.local_function_style.capitalization = pascal_case + +# By default, name items with PascalCase +dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members +dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style + +dotnet_naming_symbols.all_members.applicable_kinds = * + +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# CSharp code style settings: +[*.cs] +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left + +# Prefer "var" everywhere +dotnet_diagnostic.IDE0007.severity = warning +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = true:warning + +# Prefer method-like constructs to have a block body +csharp_style_expression_bodied_methods = false:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = false:none + +# Prefer property-like constructs to have an expression-body +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_accessors = true:warning + +# Suggest more modern language features when available +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_throw_expression = true:warning +csharp_style_conditional_delegate_call = true:suggestion + +# Newline settings +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Spacing +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_around_binary_operators = before_and_after +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false + +# Blocks are allowed +csharp_prefer_braces = true:silent +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +# IDE0035: Remove unreachable code +dotnet_diagnostic.IDE0035.severity = warning + +# IDE0036: Order modifiers +dotnet_diagnostic.IDE0036.severity = warning + +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0040.severity = warning + +# IDE0043: Format string contains invalid placeholder +dotnet_diagnostic.IDE0043.severity = warning + +# IDE0044: Make field readonly +dotnet_diagnostic.IDE0044.severity = warning + +# CONSIDER: Are IDE0051 and IDE0052 too noisy to be warnings for IDE editing scenarios? Should they be made build-only warnings? +# IDE0051: Remove unused private member +dotnet_diagnostic.IDE0051.severity = warning + +# IDE0052: Remove unread private member +dotnet_diagnostic.IDE0052.severity = warning + +# IDE0059: Unnecessary assignment to a value +dotnet_diagnostic.IDE0059.severity = warning + +# IDE0060: Remove unused parameter +dotnet_diagnostic.IDE0060.severity = warning + +# IDE0005: Using directive is unnecessary +dotnet_diagnostic.IDE0005.severity = warning + +# CA1027: Mark enums with FlagsAttribute +dotnet_diagnostic.CA1027.severity = error + +# CA1028: Enum storage should be Int32 +dotnet_diagnostic.CA1028.severity = error + +dotnet_diagnostic.RS0016.severity = warning \ No newline at end of file diff --git a/src/Assets/dotnet-format/for_code_formatter/generator_solution/console_app/Program.cs b/src/Assets/dotnet-format/for_code_formatter/generator_solution/console_app/Program.cs new file mode 100644 index 000000000000..7ddf97445b27 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/generator_solution/console_app/Program.cs @@ -0,0 +1,2 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. +Console.WriteLine("Hello World!"); diff --git a/src/Assets/dotnet-format/for_code_formatter/generator_solution/console_app/PublicAPI.Shipped.txt b/src/Assets/dotnet-format/for_code_formatter/generator_solution/console_app/PublicAPI.Shipped.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Assets/dotnet-format/for_code_formatter/generator_solution/console_app/PublicAPI.Unshipped.txt b/src/Assets/dotnet-format/for_code_formatter/generator_solution/console_app/PublicAPI.Unshipped.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Assets/dotnet-format/for_code_formatter/generator_solution/console_app/console_app.csproj b/src/Assets/dotnet-format/for_code_formatter/generator_solution/console_app/console_app.csproj new file mode 100644 index 000000000000..a7cb4f8d71a0 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/generator_solution/console_app/console_app.csproj @@ -0,0 +1,21 @@ + + + + Exe + net6.0 + enable + enable + + + + + Analyzer + false + + + + all + runtime; build; native; contentfiles; analyzers + + + diff --git a/src/Assets/dotnet-format/for_code_formatter/generator_solution/generator_lib/GreeterSourceGenerator.cs b/src/Assets/dotnet-format/for_code_formatter/generator_solution/generator_lib/GreeterSourceGenerator.cs new file mode 100644 index 000000000000..d3ef5c73ad1b --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/generator_solution/generator_lib/GreeterSourceGenerator.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis; + +namespace generator_lib +{ + [Generator] + public class GreeterSourceGenerator : ISourceGenerator + { + public void Initialize(GeneratorInitializationContext context) + { + } + + public void Execute(GeneratorExecutionContext context) + { + var code = @" +public class Greeter +{ + public void Greet() { } +}"; + + context.AddSource("Greeter.cs", code); + } + } +} diff --git a/src/Assets/dotnet-format/for_code_formatter/generator_solution/generator_lib/generator_lib.csproj b/src/Assets/dotnet-format/for_code_formatter/generator_solution/generator_lib/generator_lib.csproj new file mode 100644 index 000000000000..bdc3cc91384f --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/generator_solution/generator_lib/generator_lib.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/src/Assets/dotnet-format/for_code_formatter/generator_solution/generator_solution.sln b/src/Assets/dotnet-format/for_code_formatter/generator_solution/generator_solution.sln new file mode 100644 index 000000000000..21e99f4e6ab7 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/generator_solution/generator_solution.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "generator_lib", "generator_lib\generator_lib.csproj", "{0E1F4CDB-4124-42AC-831E-5405964FBE56}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "console_app", "console_app\console_app.csproj", "{D5160F09-32CB-48FA-9884-2ECF8F7F8629}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0E1F4CDB-4124-42AC-831E-5405964FBE56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E1F4CDB-4124-42AC-831E-5405964FBE56}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E1F4CDB-4124-42AC-831E-5405964FBE56}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E1F4CDB-4124-42AC-831E-5405964FBE56}.Release|Any CPU.Build.0 = Release|Any CPU + {D5160F09-32CB-48FA-9884-2ECF8F7F8629}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5160F09-32CB-48FA-9884-2ECF8F7F8629}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5160F09-32CB-48FA-9884-2ECF8F7F8629}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5160F09-32CB-48FA-9884-2ECF8F7F8629}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/Assets/dotnet-format/for_code_formatter/unformatted_project/.editorconfig b/src/Assets/dotnet-format/for_code_formatter/unformatted_project/.editorconfig new file mode 100644 index 000000000000..a5534703b411 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/unformatted_project/.editorconfig @@ -0,0 +1,146 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[{Program.cs,GeneratedTest.cs,other_items/*.cs,obj/**/*.cs}] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 2 +indent_style = space +tab_width = 2 + +# New line preferences +# end_of_line = crlf +insert_final_newline = false + +#### .NET Coding Conventions #### + +# this. and Me. preferences +dotnet_style_qualification_for_event = false:silent +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_property = false:silent + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent + +# Expression-level preferences +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_throw_expression = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion + +# Field preferences +dotnet_style_readonly_field = true:suggestion + +# Parameter preferences +dotnet_code_quality_unused_parameters = all:suggestion + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false:silent +csharp_style_var_for_built_in_types = false:silent +csharp_style_var_when_type_is_apparent = false:silent + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async + +# Code-block preferences +csharp_prefer_braces = true:silent + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + diff --git a/src/Assets/dotnet-format/for_code_formatter/unformatted_project/GeneratedTest.cs b/src/Assets/dotnet-format/for_code_formatter/unformatted_project/GeneratedTest.cs new file mode 100644 index 000000000000..ba09d1a5e22e --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/unformatted_project/GeneratedTest.cs @@ -0,0 +1,17 @@ + +// This is a file that should be ignored. +// +// + +using System; + +namespace for_code_formatter +{ + class GeneratedTest + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/src/Assets/dotnet-format/for_code_formatter/unformatted_project/Program.cs b/src/Assets/dotnet-format/for_code_formatter/unformatted_project/Program.cs new file mode 100644 index 000000000000..e4cfa76c0046 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/unformatted_project/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace for_code_formatter +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/src/Assets/dotnet-format/for_code_formatter/unformatted_project/ignored_items/IgnoredClass.cs b/src/Assets/dotnet-format/for_code_formatter/unformatted_project/ignored_items/IgnoredClass.cs new file mode 100644 index 000000000000..27ef47eba24b --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/unformatted_project/ignored_items/IgnoredClass.cs @@ -0,0 +1,12 @@ +using System; + +namespace for_code_formatter +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/src/Assets/dotnet-format/for_code_formatter/unformatted_project/other_items/OtherClass.cs b/src/Assets/dotnet-format/for_code_formatter/unformatted_project/other_items/OtherClass.cs new file mode 100644 index 000000000000..e4cfa76c0046 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/unformatted_project/other_items/OtherClass.cs @@ -0,0 +1,12 @@ +using System; + +namespace for_code_formatter +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/src/Assets/dotnet-format/for_code_formatter/unformatted_project/unformatted_project.csproj b/src/Assets/dotnet-format/for_code_formatter/unformatted_project/unformatted_project.csproj new file mode 100644 index 000000000000..c73e0d1692ab --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/unformatted_project/unformatted_project.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp3.1 + + + diff --git a/src/Assets/dotnet-format/for_code_formatter/unformatted_solution/unformatted_solution.sln b/src/Assets/dotnet-format/for_code_formatter/unformatted_solution/unformatted_solution.sln new file mode 100644 index 000000000000..4d2d298a7ca0 --- /dev/null +++ b/src/Assets/dotnet-format/for_code_formatter/unformatted_solution/unformatted_solution.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28621.142 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "unformatted_project", "..\unformatted_project\unformatted_project.csproj", "{EE13F2F8-3254-46A8-8A49-02C428EDE60A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EE13F2F8-3254-46A8-8A49-02C428EDE60A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE13F2F8-3254-46A8-8A49-02C428EDE60A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C0AFE45C-FD8E-4E3B-9300-8C067DE00C01} + EndGlobalSection +EndGlobal diff --git a/src/Assets/dotnet-format/for_workspace_finder/multiple_projects/project_a.csproj b/src/Assets/dotnet-format/for_workspace_finder/multiple_projects/project_a.csproj new file mode 100644 index 000000000000..c73e0d1692ab --- /dev/null +++ b/src/Assets/dotnet-format/for_workspace_finder/multiple_projects/project_a.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp3.1 + + + diff --git a/src/Assets/dotnet-format/for_workspace_finder/multiple_projects/project_b.csproj b/src/Assets/dotnet-format/for_workspace_finder/multiple_projects/project_b.csproj new file mode 100644 index 000000000000..c73e0d1692ab --- /dev/null +++ b/src/Assets/dotnet-format/for_workspace_finder/multiple_projects/project_b.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp3.1 + + + diff --git a/src/Assets/dotnet-format/for_workspace_finder/multiple_solutions/solution_a.sln b/src/Assets/dotnet-format/for_workspace_finder/multiple_solutions/solution_a.sln new file mode 100644 index 000000000000..8a505bbd7251 --- /dev/null +++ b/src/Assets/dotnet-format/for_workspace_finder/multiple_solutions/solution_a.sln @@ -0,0 +1,19 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28621.142 +MinimumVisualStudioVersion = 10.0.40219.1 +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7269AAEA-44D1-4F69-AD8D-290516915A59} + EndGlobalSection +EndGlobal diff --git a/src/Assets/dotnet-format/for_workspace_finder/multiple_solutions/solution_b.sln b/src/Assets/dotnet-format/for_workspace_finder/multiple_solutions/solution_b.sln new file mode 100644 index 000000000000..8a505bbd7251 --- /dev/null +++ b/src/Assets/dotnet-format/for_workspace_finder/multiple_solutions/solution_b.sln @@ -0,0 +1,19 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28621.142 +MinimumVisualStudioVersion = 10.0.40219.1 +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7269AAEA-44D1-4F69-AD8D-290516915A59} + EndGlobalSection +EndGlobal diff --git a/src/Assets/dotnet-format/for_workspace_finder/no_project_or_solution/.keep b/src/Assets/dotnet-format/for_workspace_finder/no_project_or_solution/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Assets/dotnet-format/for_workspace_finder/project_and_solution/project_and_solution.csproj b/src/Assets/dotnet-format/for_workspace_finder/project_and_solution/project_and_solution.csproj new file mode 100644 index 000000000000..c73e0d1692ab --- /dev/null +++ b/src/Assets/dotnet-format/for_workspace_finder/project_and_solution/project_and_solution.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp3.1 + + + diff --git a/src/Assets/dotnet-format/for_workspace_finder/project_and_solution/project_and_solution.sln b/src/Assets/dotnet-format/for_workspace_finder/project_and_solution/project_and_solution.sln new file mode 100644 index 000000000000..d03dba527548 --- /dev/null +++ b/src/Assets/dotnet-format/for_workspace_finder/project_and_solution/project_and_solution.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28621.142 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "project_and_solution", "project_and_solution.csproj", "{A9C884AA-4750-4533-8FBF-9437679082CB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A9C884AA-4750-4533-8FBF-9437679082CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A9C884AA-4750-4533-8FBF-9437679082CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A9C884AA-4750-4533-8FBF-9437679082CB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A9C884AA-4750-4533-8FBF-9437679082CB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7269AAEA-44D1-4F69-AD8D-290516915A59} + EndGlobalSection +EndGlobal diff --git a/src/Assets/dotnet-format/for_workspace_finder/single_project/single_project.csproj b/src/Assets/dotnet-format/for_workspace_finder/single_project/single_project.csproj new file mode 100644 index 000000000000..c73e0d1692ab --- /dev/null +++ b/src/Assets/dotnet-format/for_workspace_finder/single_project/single_project.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp3.1 + + + diff --git a/src/Assets/dotnet-format/for_workspace_finder/single_solution/single_solution.sln b/src/Assets/dotnet-format/for_workspace_finder/single_solution/single_solution.sln new file mode 100644 index 000000000000..8a505bbd7251 --- /dev/null +++ b/src/Assets/dotnet-format/for_workspace_finder/single_solution/single_solution.sln @@ -0,0 +1,19 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28621.142 +MinimumVisualStudioVersion = 10.0.40219.1 +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7269AAEA-44D1-4F69-AD8D-290516915A59} + EndGlobalSection +EndGlobal diff --git a/src/BuiltInTools/dotnet-format.slnf b/src/BuiltInTools/dotnet-format.slnf new file mode 100644 index 000000000000..2fed61b4d85a --- /dev/null +++ b/src/BuiltInTools/dotnet-format.slnf @@ -0,0 +1,9 @@ +{ + "solution": { + "path": "..\\..\\sdk.sln", + "projects": [ + "src\\BuiltInTools\\dotnet-format\\dotnet-format.csproj", + "test\\dotnet-format.Tests\\tests\\dotnet-format.UnitTests.csproj" + ] + } +} \ No newline at end of file diff --git a/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerFinderHelpers.cs b/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerFinderHelpers.cs new file mode 100644 index 000000000000..28c94dd368d6 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerFinderHelpers.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Reflection; + +using Microsoft.CodeAnalysis.CodeFixes; + +namespace Microsoft.CodeAnalysis.Tools.Analyzers +{ + internal static class AnalyzerFinderHelpers + { + public static ImmutableArray LoadFixers(IEnumerable assemblies, string language) + { + + return assemblies + .SelectMany(GetConcreteTypes) + .Where(t => typeof(CodeFixProvider).IsAssignableFrom(t)) + .Where(t => IsExportedForLanguage(t, language)) + .Select(CreateInstanceOfCodeFix) + .OfType() + .ToImmutableArray(); + } + + private static bool IsExportedForLanguage(Type codeFixProvider, string language) + { + var exportAttribute = codeFixProvider.GetCustomAttribute(inherit: false); + return exportAttribute is not null && exportAttribute.Languages.Contains(language); + } + + private static CodeFixProvider? CreateInstanceOfCodeFix(Type codeFixProvider) + { + try + { + return (CodeFixProvider?)Activator.CreateInstance(codeFixProvider); + } + catch + { + return null; + } + } + + private static IEnumerable GetConcreteTypes(Assembly assembly) + { + try + { + var concreteTypes = assembly + .GetTypes() + .Where(type => !type.GetTypeInfo().IsInterface + && !type.GetTypeInfo().IsAbstract + && !type.GetTypeInfo().ContainsGenericParameters); + + // Realize the collection to ensure exceptions are caught + return concreteTypes.ToList(); + } + catch + { + return Type.EmptyTypes; + } + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerFormatter.cs b/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerFormatter.cs new file mode 100644 index 000000000000..9b78488b0aeb --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerFormatter.cs @@ -0,0 +1,361 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Diagnostics; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Tools.Formatters; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Analyzers +{ + internal class AnalyzerFormatter : ICodeFormatter + { + public static AnalyzerFormatter CodeStyleFormatter => new AnalyzerFormatter( + Resources.Code_Style, + FixCategory.CodeStyle, + includeCompilerDiagnostics: false, + new CodeStyleInformationProvider(), + new AnalyzerRunner(), + new SolutionCodeFixApplier()); + + public static AnalyzerFormatter ThirdPartyFormatter => new AnalyzerFormatter( + Resources.Analyzer_Reference, + FixCategory.Analyzers, + includeCompilerDiagnostics: true, + new AnalyzerReferenceInformationProvider(), + new AnalyzerRunner(), + new SolutionCodeFixApplier()); + + private readonly string _name; + private readonly bool _includeCompilerDiagnostics; + private readonly IAnalyzerInformationProvider _informationProvider; + private readonly IAnalyzerRunner _runner; + private readonly ICodeFixApplier _applier; + + public FixCategory Category { get; } + + public AnalyzerFormatter( + string name, + FixCategory category, + bool includeCompilerDiagnostics, + IAnalyzerInformationProvider informationProvider, + IAnalyzerRunner runner, + ICodeFixApplier applier) + { + _name = name; + Category = category; + _includeCompilerDiagnostics = includeCompilerDiagnostics; + _informationProvider = informationProvider; + _runner = runner; + _applier = applier; + } + + public async Task FormatAsync( + Workspace workspace, + Solution solution, + ImmutableArray formattableDocuments, + FormatOptions formatOptions, + ILogger logger, + List formattedFiles, + CancellationToken cancellationToken) + { + var projectAnalyzersAndFixers = _informationProvider.GetAnalyzersAndFixers(workspace, solution, formatOptions, logger); + if (projectAnalyzersAndFixers.IsEmpty) + { + return solution; + } + + var allFixers = projectAnalyzersAndFixers.Values.SelectMany(analyzersAndFixers => analyzersAndFixers.Fixers).ToImmutableArray(); + + // Only include compiler diagnostics if we have an associated fixer that supports FixAllScope.Solution + var fixableCompilerDiagnostics = _includeCompilerDiagnostics + ? allFixers + .Where(codefix => codefix.GetFixAllProvider()?.GetSupportedFixAllScopes()?.Contains(FixAllScope.Solution) == true) + .SelectMany(codefix => codefix.FixableDiagnosticIds.Where(id => id.StartsWith("CS") || id.StartsWith("BC"))) + .ToImmutableHashSet() + : ImmutableHashSet.Empty; + + // Filter compiler diagnostics + if (!fixableCompilerDiagnostics.IsEmpty && !formatOptions.Diagnostics.IsEmpty) + { + fixableCompilerDiagnostics.Intersect(formatOptions.Diagnostics); + } + + var analysisStopwatch = Stopwatch.StartNew(); + logger.LogTrace(Resources.Running_0_analysis, _name); + var formattablePaths = await GetFormattablePathsAsync(solution, formattableDocuments, cancellationToken).ConfigureAwait(false); + + logger.LogTrace(Resources.Determining_diagnostics); + + var severity = _informationProvider.GetSeverity(formatOptions); + + // Filter to analyzers that report diagnostics with equal or greater severity. + var projectAnalyzers = await FilterAnalyzersAsync(solution, projectAnalyzersAndFixers, formattablePaths, severity, formatOptions.Diagnostics, formatOptions.ExcludeDiagnostics, cancellationToken).ConfigureAwait(false); + + // Determine which diagnostics are being reported for each project. + var projectDiagnostics = await GetProjectDiagnosticsAsync(solution, projectAnalyzers, formattablePaths, formatOptions, severity, fixableCompilerDiagnostics, logger, formattedFiles, cancellationToken).ConfigureAwait(false); + + var projectDiagnosticsMS = analysisStopwatch.ElapsedMilliseconds; + logger.LogTrace(Resources.Complete_in_0_ms, projectDiagnosticsMS); + + // Only run code fixes when we are saving changes. + if (formatOptions.SaveFormattedFiles) + { + logger.LogTrace(Resources.Fixing_diagnostics); + + // Run each analyzer individually and apply fixes if possible. + solution = await FixDiagnosticsAsync(solution, projectAnalyzers, allFixers, projectDiagnostics, formattablePaths, formatOptions, severity, fixableCompilerDiagnostics, logger, cancellationToken).ConfigureAwait(false); + + var fixDiagnosticsMS = analysisStopwatch.ElapsedMilliseconds - projectDiagnosticsMS; + logger.LogTrace(Resources.Complete_in_0_ms, fixDiagnosticsMS); + } + + logger.LogTrace(Resources.Analysis_complete_in_0ms_, analysisStopwatch.ElapsedMilliseconds); + + return solution; + + async static Task> GetFormattablePathsAsync(Solution solution, ImmutableArray formattableDocuments, CancellationToken cancellationToken) + { + var formattablePaths = ImmutableHashSet.CreateBuilder(); + + foreach (var documentId in formattableDocuments) + { + var document = solution.GetDocument(documentId); + if (document is null) + { + document = await solution.GetSourceGeneratedDocumentAsync(documentId, cancellationToken).ConfigureAwait(false); + + if (document is null) + { + continue; + } + } + + formattablePaths.Add(document.FilePath!); + } + + return formattablePaths.ToImmutable(); + } + } + + private async Task>> GetProjectDiagnosticsAsync( + Solution solution, + ImmutableDictionary> projectAnalyzers, + ImmutableHashSet formattablePaths, + FormatOptions options, + DiagnosticSeverity severity, + ImmutableHashSet fixableCompilerDiagnostics, + ILogger logger, + List formattedFiles, + CancellationToken cancellationToken) + { + var result = new CodeAnalysisResult(options.Diagnostics, options.ExcludeDiagnostics); + var projects = options.WorkspaceType == WorkspaceType.Solution + ? solution.Projects + : solution.Projects.Where(project => project.FilePath == options.WorkspaceFilePath); + foreach (var project in projects) + { + var analyzers = projectAnalyzers[project.Id]; + if (analyzers.IsEmpty) + { + continue; + } + + // Run all the filtered analyzers to determine which are reporting diagnostic. + await _runner.RunCodeAnalysisAsync(result, analyzers, project, formattablePaths, severity, fixableCompilerDiagnostics, logger, cancellationToken).ConfigureAwait(false); + } + + LogDiagnosticLocations(solution, result.Diagnostics.SelectMany(kvp => kvp.Value), options.SaveFormattedFiles, options.ChangesAreErrors, logger, options.LogLevel, formattedFiles); + + return result.Diagnostics.ToImmutableDictionary(kvp => kvp.Key.Id, kvp => kvp.Value.Select(diagnostic => diagnostic.Id).ToImmutableHashSet()); + + static void LogDiagnosticLocations(Solution solution, IEnumerable diagnostics, bool saveFormattedFiles, bool changesAreErrors, ILogger logger, LogLevel logLevel, List formattedFiles) + { + foreach (var diagnostic in diagnostics) + { + var document = solution.GetDocument(diagnostic.Location.SourceTree); + if (document is null) + { + continue; + } + + var mappedLineSpan = diagnostic.Location.GetMappedLineSpan(); + var diagnosticPosition = mappedLineSpan.StartLinePosition; + + if (!saveFormattedFiles || logLevel == LogLevel.Debug) + { + logger.LogDiagnosticIssue(document, diagnosticPosition, diagnostic, changesAreErrors); + } + + formattedFiles.Add(new FormattedFile(document, new[] { new FileChange(diagnosticPosition, diagnostic.Id, $"{diagnostic.Severity.ToString().ToLower()} {diagnostic.Id}: {diagnostic.GetMessage()}") })); + } + } + } + + private async Task FixDiagnosticsAsync( + Solution solution, + ImmutableDictionary> projectAnalyzers, + ImmutableArray allFixers, + ImmutableDictionary> projectDiagnostics, + ImmutableHashSet formattablePaths, + FormatOptions options, + DiagnosticSeverity severity, + ImmutableHashSet fixableCompilerDiagnostics, + ILogger logger, + CancellationToken cancellationToken) + { + // Determine the reported diagnostic ids + var reportedDiagnostics = projectDiagnostics.SelectMany(kvp => kvp.Value).Distinct().ToImmutableArray(); + if (reportedDiagnostics.IsEmpty) + { + return solution; + } + + var fixersById = CreateFixerMap(reportedDiagnostics, allFixers); + + // We need to run each codefix iteratively so ensure that all diagnostics are found and fixed. + foreach (var diagnosticId in reportedDiagnostics) + { + var codefixes = fixersById[diagnosticId]; + + // If there is no codefix, there is no reason to run analysis again. + if (codefixes.IsEmpty) + { + logger.LogWarning(Resources.Unable_to_fix_0_No_associated_code_fix_found, diagnosticId); + continue; + } + + var result = new CodeAnalysisResult(options.Diagnostics, options.ExcludeDiagnostics); + foreach (var project in solution.Projects) + { + // Only run analysis on projects that had previously reported the diagnostic + if (!projectDiagnostics.TryGetValue(project.Id, out var diagnosticIds) + || !diagnosticIds.Contains(diagnosticId)) + { + continue; + } + + var analyzers = projectAnalyzers[project.Id] + .Where(analyzer => analyzer.SupportedDiagnostics.Any(descriptor => descriptor.Id == diagnosticId)) + .ToImmutableArray(); + await _runner.RunCodeAnalysisAsync(result, analyzers, project, formattablePaths, severity, fixableCompilerDiagnostics, logger, cancellationToken).ConfigureAwait(false); + } + + var hasDiagnostics = result.Diagnostics.Any(kvp => kvp.Value.Count > 0); + if (hasDiagnostics) + { + foreach (var codefix in codefixes) + { + var changedSolution = await _applier.ApplyCodeFixesAsync(solution, result, codefix, diagnosticId, logger, cancellationToken).ConfigureAwait(false); + if (changedSolution.GetChanges(solution).Any()) + { + solution = changedSolution; + } + } + } + } + + return solution; + + static ImmutableDictionary> CreateFixerMap( + ImmutableArray diagnosticIds, + ImmutableArray fixers) + { + return diagnosticIds.ToImmutableDictionary( + id => id, + id => fixers + .Where(fixer => ContainsFixableId(fixer, id)) + .ToImmutableArray()); + } + + static bool ContainsFixableId(CodeFixProvider fixer, string id) + { + // The unnecessary imports diagnostic and fixer use a special diagnostic id. + if (id == "IDE0005" && fixer.FixableDiagnosticIds.Contains("RemoveUnnecessaryImportsFixable")) + { + return true; + } + + return fixer.FixableDiagnosticIds.Contains(id); + } + } + + internal static async Task>> FilterAnalyzersAsync( + Solution solution, + ImmutableDictionary projectAnalyzersAndFixers, + ImmutableHashSet formattablePaths, + DiagnosticSeverity minimumSeverity, + ImmutableHashSet diagnostics, + ImmutableHashSet excludeDiagnostics, + CancellationToken cancellationToken) + { + // We only want to run analyzers for each project that have the potential for reporting a diagnostic with + // a severity equal to or greater than specified. + var projectAnalyzers = ImmutableDictionary.CreateBuilder>(); + foreach (var projectId in projectAnalyzersAndFixers.Keys) + { + var project = solution.GetProject(projectId); + if (project is null) + { + continue; + } + + // Skip if the project does not contain any of the formattable paths. + if (!project.Documents.Any(d => d.FilePath is not null && formattablePaths.Contains(d.FilePath))) + { + projectAnalyzers.Add(projectId, ImmutableArray.Empty); + continue; + } + + var analyzers = ImmutableArray.CreateBuilder(); + + // Filter analyzers by project's language + var filteredAnalyzer = projectAnalyzersAndFixers[projectId].Analyzers + .Where(analyzer => DoesAnalyzerSupportLanguage(analyzer, project.Language)); + foreach (var analyzer in filteredAnalyzer) + { + // Filter by excluded diagnostics + if (!excludeDiagnostics.IsEmpty && + analyzer.SupportedDiagnostics.All(descriptor => excludeDiagnostics.Contains(descriptor.Id))) + { + continue; + } + + // Filter by diagnostics + if (!diagnostics.IsEmpty && + !analyzer.SupportedDiagnostics.Any(descriptor => diagnostics.Contains(descriptor.Id))) + { + continue; + } + + // Always run naming style analyzers because we cannot determine potential severity. + // The reported diagnostics will be filtered by severity when they are run. + if (analyzer.GetType().FullName?.EndsWith("NamingStyleDiagnosticAnalyzer") == true) + { + analyzers.Add(analyzer); + continue; + } + + var severity = await analyzer.GetSeverityAsync(project, formattablePaths, cancellationToken).ConfigureAwait(false); + if (severity >= minimumSeverity) + { + analyzers.Add(analyzer); + } + } + + projectAnalyzers.Add(projectId, analyzers.ToImmutableArray()); + } + + return projectAnalyzers.ToImmutableDictionary(); + } + + private static bool DoesAnalyzerSupportLanguage(DiagnosticAnalyzer analyzer, string language) + { + return analyzer.GetType() + .GetCustomAttributes(typeof(DiagnosticAnalyzerAttribute), true) + .OfType() + .Any(attribute => attribute.Languages.Contains(language)); + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerOptionExtensions.cs b/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerOptionExtensions.cs new file mode 100644 index 000000000000..9f5840e9e3c1 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerOptionExtensions.cs @@ -0,0 +1,207 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Tools.Analyzers; + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + internal static class AnalyzerOptionsExtensions + { + internal const string DotnetDiagnosticPrefix = "dotnet_diagnostic"; + internal const string DotnetAnalyzerDiagnosticPrefix = "dotnet_analyzer_diagnostic"; + internal const string CategoryPrefix = "category"; + internal const string SeveritySuffix = "severity"; + + internal const string DotnetAnalyzerDiagnosticSeverityKey = DotnetAnalyzerDiagnosticPrefix + "." + SeveritySuffix; + + internal static string GetDiagnosticIdBasedDotnetAnalyzerDiagnosticSeverityKey(string diagnosticId) + => $"{DotnetDiagnosticPrefix}.{diagnosticId}.{SeveritySuffix}"; + internal static string GetCategoryBasedDotnetAnalyzerDiagnosticSeverityKey(string category) + => $"{DotnetAnalyzerDiagnosticPrefix}.{CategoryPrefix}-{category}.{SeveritySuffix}"; + + /// + /// Tries to get configured severity for the given + /// for the given from analyzer config options, i.e. + /// 'dotnet_diagnostic.%descriptor.Id%.severity = %severity%', + /// 'dotnet_analyzer_diagnostic.category-%RuleCategory%.severity = %severity%', + /// or + /// 'dotnet_analyzer_diagnostic.severity = %severity%' + /// + public static bool TryGetSeverityFromConfiguration( + this AnalyzerOptions? analyzerOptions, + SyntaxTree tree, + Compilation compilation, + DiagnosticDescriptor descriptor, + out ReportDiagnostic severity) + { + var diagnosticId = descriptor.Id; + + // The unnecessary imports diagnostic and fixer use a special diagnostic id. + if (diagnosticId == "RemoveUnnecessaryImportsFixable") + { + diagnosticId = "IDE0005"; + } + + // If user has explicitly configured severity for this diagnostic ID, that should be respected. + if (compilation.Options.SpecificDiagnosticOptions.TryGetValue(diagnosticId, out severity)) + { + return true; + } + + // If user has explicitly configured severity for this diagnostic ID, that should be respected. + // For example, 'dotnet_diagnostic.CA1000.severity = error' + if (compilation.Options.SyntaxTreeOptionsProvider?.TryGetDiagnosticValue(tree, diagnosticId, CancellationToken.None, out severity) == true) + { + return true; + } + + // Analyzer bulk configuration does not apply to: + // 1. Disabled by default diagnostics + // 2. Compiler diagnostics + // 3. Non-configurable diagnostics + if (analyzerOptions == null || + !descriptor.IsEnabledByDefault || + descriptor.CustomTags.Any(tag => tag == WellKnownDiagnosticTags.Compiler || tag == WellKnownDiagnosticTags.NotConfigurable)) + { + severity = default; + return false; + } + + var analyzerConfigOptions = analyzerOptions.AnalyzerConfigOptionsProvider.GetOptions(tree); + + // If user has explicitly configured default severity for the diagnostic category, that should be respected. + // For example, 'dotnet_analyzer_diagnostic.category-security.severity = error' + var categoryBasedKey = GetCategoryBasedDotnetAnalyzerDiagnosticSeverityKey(descriptor.Category); + if (analyzerConfigOptions.TryGetValue(categoryBasedKey, out var value) && + TryParseSeverity(value, out severity)) + { + return true; + } + + // Otherwise, if user has explicitly configured default severity for all analyzer diagnostics, that should be respected. + // For example, 'dotnet_analyzer_diagnostic.severity = error' + if (analyzerConfigOptions.TryGetValue(DotnetAnalyzerDiagnosticSeverityKey, out value) && + TryParseSeverity(value, out severity)) + { + return true; + } + + severity = default; + return false; + } + + /// + /// Determines whether a diagnostic is configured in the . + /// + public static bool IsDiagnosticSeverityConfigured(this AnalyzerConfigOptions analyzerConfigOptions, Project project, SyntaxTree tree, string diagnosticId, string? diagnosticCategory) + { + var optionsProvider = project.CompilationOptions?.SyntaxTreeOptionsProvider; + return (optionsProvider != null && optionsProvider.TryGetDiagnosticValue(tree, diagnosticId, CancellationToken.None, out _)) + || (diagnosticCategory != null && analyzerConfigOptions.TryGetValue(GetCategoryBasedDotnetAnalyzerDiagnosticSeverityKey(diagnosticCategory), out _)) + || analyzerConfigOptions.TryGetValue(DotnetAnalyzerDiagnosticSeverityKey, out _); + } + + /// + /// Get the configured severity for a diagnostic analyzer from the . + /// + public static DiagnosticSeverity GetDiagnosticSeverity(this AnalyzerConfigOptions analyzerConfigOptions, Project project, SyntaxTree tree, string diagnosticId, string? diagnosticCategory) + { + return analyzerConfigOptions.TryGetSeverityFromConfiguration(project, tree, diagnosticId, diagnosticCategory, out var reportSeverity) + ? reportSeverity.ToSeverity() + : DiagnosticSeverity.Hidden; + } + + /// + /// Tries to get configured severity for the given + /// for the given from analyzer config options, i.e. + /// 'dotnet_diagnostic.%descriptor.Id%.severity = %severity%', + /// 'dotnet_analyzer_diagnostic.category-%RuleCategory%.severity = %severity%', + /// or + /// 'dotnet_analyzer_diagnostic.severity = %severity%' + /// + public static bool TryGetSeverityFromConfiguration( + this AnalyzerConfigOptions? analyzerConfigOptions, + Project project, + SyntaxTree tree, + string diagnosticId, + string? diagnosticCategory, + out ReportDiagnostic severity) + { + if (analyzerConfigOptions is null) + { + severity = default; + return false; + } + + // If user has explicitly configured severity for this diagnostic ID, that should be respected. + // For example, 'dotnet_diagnostic.CA1000.severity = error' + var optionsProvider = project.CompilationOptions?.SyntaxTreeOptionsProvider; + if (optionsProvider != null && + optionsProvider.TryGetDiagnosticValue(tree, diagnosticId, CancellationToken.None, out severity)) + { + return true; + } + + string? value; + if (diagnosticCategory != null) + { + // If user has explicitly configured default severity for the diagnostic category, that should be respected. + // For example, 'dotnet_analyzer_diagnostic.category-security.severity = error' + var categoryBasedKey = GetCategoryBasedDotnetAnalyzerDiagnosticSeverityKey(diagnosticCategory); + if (analyzerConfigOptions.TryGetValue(categoryBasedKey, out value) && + TryParseSeverity(value, out severity)) + { + return true; + } + } + + // Otherwise, if user has explicitly configured default severity for all analyzer diagnostics, that should be respected. + // For example, 'dotnet_analyzer_diagnostic.severity = error' + if (analyzerConfigOptions.TryGetValue(DotnetAnalyzerDiagnosticSeverityKey, out value) && + TryParseSeverity(value, out severity)) + { + return true; + } + + severity = default; + return false; + } + + internal static bool TryParseSeverity(string value, out ReportDiagnostic severity) + { + var comparer = StringComparer.OrdinalIgnoreCase; + if (comparer.Equals(value, "default")) + { + severity = ReportDiagnostic.Default; + return true; + } + else if (comparer.Equals(value, "error")) + { + severity = ReportDiagnostic.Error; + return true; + } + else if (comparer.Equals(value, "warning")) + { + severity = ReportDiagnostic.Warn; + return true; + } + else if (comparer.Equals(value, "suggestion")) + { + severity = ReportDiagnostic.Info; + return true; + } + else if (comparer.Equals(value, "silent") || comparer.Equals(value, "refactoring")) + { + severity = ReportDiagnostic.Hidden; + return true; + } + else if (comparer.Equals(value, "none")) + { + severity = ReportDiagnostic.Suppress; + return true; + } + + severity = default; + return false; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerReferenceInformationProvider.cs b/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerReferenceInformationProvider.cs new file mode 100644 index 000000000000..47c05f9f0480 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerReferenceInformationProvider.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Reflection; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Analyzers +{ + internal class AnalyzerReferenceInformationProvider : IAnalyzerInformationProvider + { + private static readonly Dictionary s_pathsToAssemblies = new(StringComparer.OrdinalIgnoreCase); + private static readonly Dictionary s_namesToAssemblies = new(StringComparer.OrdinalIgnoreCase); + + private static readonly object s_guard = new(); + + public ImmutableDictionary GetAnalyzersAndFixers( + Workspace workspace, + Solution solution, + FormatOptions formatOptions, + ILogger logger) + { + return solution.Projects + .ToImmutableDictionary(project => project.Id, project => GetAnalyzersAndFixers(workspace, project)); + } + + private static AnalyzersAndFixers GetAnalyzersAndFixers(Workspace workspace, Project project) + { + var analyzerAssemblies = project.AnalyzerReferences + .Select(reference => TryLoadAssemblyFrom(workspace, reference.FullPath, reference)) + .OfType() + .ToImmutableArray(); + + var analyzers = project.AnalyzerReferences.SelectMany(reference => reference.GetAnalyzers(project.Language)).ToImmutableArray(); + return new AnalyzersAndFixers(analyzers, AnalyzerFinderHelpers.LoadFixers(analyzerAssemblies, project.Language)); + } + + private static Assembly? TryLoadAssemblyFrom(Workspace workspace, string? path, AnalyzerReference analyzerReference) + { + // Since we are not deploying these assemblies we need to ensure the files exist. + if (path is null || !File.Exists(path)) + { + return null; + } + + lock (s_guard) + { + if (s_pathsToAssemblies.TryGetValue(path, out var cachedAssembly)) + { + return cachedAssembly; + } + + try + { + Assembly analyzerAssembly; + if (analyzerReference is AnalyzerFileReference analyzerFileReference) + { + // If we have access to the analyzer file reference, we can update our + // cache and return the assembly. + analyzerAssembly = analyzerFileReference.GetAssembly(); + s_namesToAssemblies.TryAdd(analyzerAssembly.GetName().FullName, analyzerAssembly); + } + else + { + var analyzerService = workspace.Services.GetService() ?? throw new NotSupportedException(); + var loader = analyzerService.GetLoader(); + analyzerAssembly = loader.LoadFromPath(path); + } + + s_pathsToAssemblies.Add(path, analyzerAssembly); + + return analyzerAssembly; + } + catch + { + } + } + + return null; + } + + public DiagnosticSeverity GetSeverity(FormatOptions formatOptions) => formatOptions.AnalyzerSeverity; + } +} diff --git a/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerRunner.cs b/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerRunner.cs new file mode 100644 index 000000000000..fcf465fdcc80 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Analyzers/AnalyzerRunner.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Analyzers +{ + internal partial class AnalyzerRunner : IAnalyzerRunner + { + public Task RunCodeAnalysisAsync( + CodeAnalysisResult result, + DiagnosticAnalyzer analyzers, + Project project, + ImmutableHashSet formattableDocumentPaths, + DiagnosticSeverity severity, + ImmutableHashSet fixableCompilerDiagnostics, + ILogger logger, + CancellationToken cancellationToken) + => RunCodeAnalysisAsync(result, ImmutableArray.Create(analyzers), project, formattableDocumentPaths, severity, fixableCompilerDiagnostics, logger, cancellationToken); + + public async Task RunCodeAnalysisAsync( + CodeAnalysisResult result, + ImmutableArray analyzers, + Project project, + ImmutableHashSet formattableDocumentPaths, + DiagnosticSeverity severity, + ImmutableHashSet fixableCompilerDiagnostics, + ILogger logger, + CancellationToken cancellationToken) + { + // If are not running any analyzers and are not reporting compiler diagnostics, then there is + // nothing to report. + if (analyzers.IsEmpty && fixableCompilerDiagnostics.IsEmpty) + { + return; + } + + // For projects targeting NetStandard, the Runtime references are resolved from the project.assets.json. + // This file is generated during a `dotnet restore`. + if (!AllReferencedProjectsLoaded(project)) + { + logger.LogWarning(Resources.Required_references_did_not_load_for_0_or_referenced_project_Run_dotnet_restore_prior_to_formatting, project.Name); + return; + } + + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + if (compilation is null) + { + return; + } + + var compilerDiagnostics = !fixableCompilerDiagnostics.IsEmpty + ? compilation.GetDiagnostics(cancellationToken) + .Where(diagnostic => fixableCompilerDiagnostics.Contains(diagnostic.Id)) + .ToImmutableArray() + : ImmutableArray.Empty; + + ImmutableArray diagnostics; + if (analyzers.IsEmpty) + { + diagnostics = compilerDiagnostics; + } + else + { + logger.LogDebug(Resources.Running_0_analyzers_on_1, analyzers.Length, project.Name); + + var analyzerOptions = new CompilationWithAnalyzersOptions( + project.AnalyzerOptions, + onAnalyzerException: null, + concurrentAnalysis: true, + logAnalyzerExecutionTime: false, + reportSuppressedDiagnostics: false); + var analyzerCompilation = compilation.WithAnalyzers(analyzers, analyzerOptions); + + diagnostics = await analyzerCompilation.GetAnalyzerDiagnosticsAsync(cancellationToken).ConfigureAwait(false); + diagnostics = diagnostics.AddRange(compilerDiagnostics); + } + + // filter diagnostics + foreach (var diagnostic in diagnostics) + { + if (!diagnostic.IsSuppressed && + diagnostic.Severity >= severity && + diagnostic.Location.IsInSource && + diagnostic.Location.SourceTree != null && + formattableDocumentPaths.Contains(diagnostic.Location.SourceTree.FilePath)) + { + result.AddDiagnostic(project, diagnostic); + } + } + + return; + + static bool AllReferencedProjectsLoaded(Project project) + { + // Use mscorlib to represent Runtime references being loaded. + if (!project.MetadataReferences.Any(reference => reference.Display?.EndsWith("mscorlib.dll") == true)) + { + return false; + } + + return project.ProjectReferences + .Select(projectReference => project.Solution.GetProject(projectReference.ProjectId)) + .All(referencedProject => referencedProject != null && AllReferencedProjectsLoaded(referencedProject)); + } + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Analyzers/AnalyzersAndFixers.cs b/src/BuiltInTools/dotnet-format/Analyzers/AnalyzersAndFixers.cs new file mode 100644 index 000000000000..af839bee5291 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Analyzers/AnalyzersAndFixers.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.Tools.Analyzers +{ + internal struct AnalyzersAndFixers + { + public ImmutableArray Analyzers { get; } + public ImmutableArray Fixers { get; } + + public AnalyzersAndFixers(ImmutableArray analyzers, ImmutableArray fixers) + { + Analyzers = analyzers; + Fixers = fixers; + } + + public void Deconstruct( + out ImmutableArray analyzers, + out ImmutableArray fixers) + { + analyzers = Analyzers; + fixers = Fixers; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Analyzers/CodeAnalysisResult.cs b/src/BuiltInTools/dotnet-format/Analyzers/CodeAnalysisResult.cs new file mode 100644 index 000000000000..60752fe360d6 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Analyzers/CodeAnalysisResult.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.Tools.Analyzers +{ + internal class CodeAnalysisResult + { + private readonly Dictionary> _dictionary = new(); + private readonly ImmutableHashSet _diagnostics; + private readonly ImmutableHashSet _excludeDiagnostics; + + public CodeAnalysisResult(ImmutableHashSet diagnostics, ImmutableHashSet excludeDiagnostics) + { + _diagnostics = diagnostics; + _excludeDiagnostics = excludeDiagnostics; + } + + internal void AddDiagnostic(Project project, Diagnostic diagnostic) + { + // Ignore excluded diagnostics + if (!_excludeDiagnostics.IsEmpty && _excludeDiagnostics.Contains(diagnostic.Id)) + { + return; + } + + if (!_diagnostics.IsEmpty && !_diagnostics.Contains(diagnostic.Id)) + { + return; + } + + if (!_dictionary.ContainsKey(project)) + { + _dictionary.Add(project, new List() { diagnostic }); + } + else + { + _dictionary[project].Add(diagnostic); + } + } + + public IReadOnlyDictionary> Diagnostics + => _dictionary; + } +} diff --git a/src/BuiltInTools/dotnet-format/Analyzers/CodeStyleInformationProvider.cs b/src/BuiltInTools/dotnet-format/Analyzers/CodeStyleInformationProvider.cs new file mode 100644 index 000000000000..701f16410335 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Analyzers/CodeStyleInformationProvider.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Reflection; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Analyzers +{ + internal class CodeStyleInformationProvider : IAnalyzerInformationProvider + { + private static readonly string s_executingPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty; + + private readonly string _featuresPath = Path.Combine(s_executingPath, "Microsoft.CodeAnalysis.Features.dll"); + private readonly string _featuresCSharpPath = Path.Combine(s_executingPath, "Microsoft.CodeAnalysis.CSharp.Features.dll"); + private readonly string _featuresVisualBasicPath = Path.Combine(s_executingPath, "Microsoft.CodeAnalysis.VisualBasic.Features.dll"); + + public ImmutableDictionary GetAnalyzersAndFixers( + Workspace workspace, + Solution solution, + FormatOptions formatOptions, + ILogger logger) + { + var analyzerService = workspace.Services.GetService() ?? throw new NotSupportedException(); + var analyzerAssemblyLoader = analyzerService.GetLoader(); + var references = new[] + { + _featuresPath, + _featuresCSharpPath, + _featuresVisualBasicPath, + } + .Select(path => new AnalyzerFileReference(path, analyzerAssemblyLoader)); + + var analyzersByLanguage = new Dictionary(); + return solution.Projects + .ToImmutableDictionary( + project => project.Id, + project => + { + if (!analyzersByLanguage.TryGetValue(project.Language, out var analyzersAndFixers)) + { + var analyzers = references.SelectMany(reference => reference.GetAnalyzers(project.Language)).ToImmutableArray(); + var codeFixes = AnalyzerFinderHelpers.LoadFixers(references.Select(reference => reference.GetAssembly()), project.Language); + analyzersAndFixers = new AnalyzersAndFixers(analyzers, codeFixes); + analyzersByLanguage.Add(project.Language, analyzersAndFixers); + } + + return analyzersAndFixers; + }); + } + + public DiagnosticSeverity GetSeverity(FormatOptions formatOptions) => formatOptions.CodeStyleSeverity; + } +} diff --git a/src/BuiltInTools/dotnet-format/Analyzers/Extensions.cs b/src/BuiltInTools/dotnet-format/Analyzers/Extensions.cs new file mode 100644 index 000000000000..d2bf2c6cab76 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Analyzers/Extensions.cs @@ -0,0 +1,175 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections; +using System.Collections.Immutable; +using System.Reflection; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.Tools.Analyzers +{ + public static class Extensions + { + private static Assembly MicrosoftCodeAnalysisFeaturesAssembly { get; } + private static Type IDEDiagnosticIdToOptionMappingHelperType { get; } + private static MethodInfo TryGetMappedOptionsMethod { get; } + + static Extensions() + { + MicrosoftCodeAnalysisFeaturesAssembly = Assembly.Load(new AssemblyName("Microsoft.CodeAnalysis.Features")); + IDEDiagnosticIdToOptionMappingHelperType = MicrosoftCodeAnalysisFeaturesAssembly.GetType("Microsoft.CodeAnalysis.Diagnostics.IDEDiagnosticIdToOptionMappingHelper")!; + TryGetMappedOptionsMethod = IDEDiagnosticIdToOptionMappingHelperType.GetMethod("TryGetMappedOptions", BindingFlags.Static | BindingFlags.Public)!; + } + + public static bool Any(this SolutionChanges solutionChanges) + => solutionChanges.GetProjectChanges() + .Any(x => x.GetChangedDocuments().Any() || x.GetChangedAdditionalDocuments().Any()); + + /// + /// Get the highest possible severity for any formattable document in the project. + /// + public static async Task GetSeverityAsync( + this DiagnosticAnalyzer analyzer, + Project project, + ImmutableHashSet formattablePaths, + CancellationToken cancellationToken) + { + var severity = DiagnosticSeverity.Hidden; + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + if (compilation is null) + { + return severity; + } + + foreach (var document in project.Documents) + { + // Is the document formattable? + if (document.FilePath is null || !formattablePaths.Contains(document.FilePath)) + { + continue; + } + + var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); + + var documentSeverity = analyzer.GetSeverity(document, project.AnalyzerOptions, options, compilation); + if (documentSeverity > severity) + { + severity = documentSeverity; + } + } + + return severity; + } + + public static DiagnosticSeverity ToSeverity(this ReportDiagnostic reportDiagnostic) + { + return reportDiagnostic switch + { + ReportDiagnostic.Error => DiagnosticSeverity.Error, + ReportDiagnostic.Warn => DiagnosticSeverity.Warning, + ReportDiagnostic.Info => DiagnosticSeverity.Info, + _ => DiagnosticSeverity.Hidden + }; + } + + private static DiagnosticSeverity GetSeverity( + this DiagnosticAnalyzer analyzer, + Document document, + AnalyzerOptions? analyzerOptions, + OptionSet options, + Compilation compilation) + { + var severity = DiagnosticSeverity.Hidden; + + if (!document.TryGetSyntaxTree(out var tree)) + { + return severity; + } + + foreach (var descriptor in analyzer.SupportedDiagnostics) + { + if (severity == DiagnosticSeverity.Error) + { + break; + } + + if (analyzerOptions.TryGetSeverityFromConfiguration(tree, compilation, descriptor, out var reportDiagnostic)) + { + var configuredSeverity = reportDiagnostic.ToSeverity(); + if (configuredSeverity > severity) + { + severity = configuredSeverity; + } + + continue; + } + + if (TryGetSeverityFromCodeStyleOption(descriptor, compilation, options, out var codeStyleSeverity)) + { + if (codeStyleSeverity > severity) + { + severity = codeStyleSeverity; + } + + continue; + } + + if (descriptor.DefaultSeverity > severity) + { + severity = descriptor.DefaultSeverity; + } + } + + return severity; + + static bool TryGetSeverityFromCodeStyleOption( + DiagnosticDescriptor descriptor, + Compilation compilation, + OptionSet options, + out DiagnosticSeverity severity) + { + severity = DiagnosticSeverity.Hidden; + + var parameters = new object?[] { descriptor.Id, compilation.Language, null }; + var result = (bool)(TryGetMappedOptionsMethod.Invoke(null, parameters) ?? false); + + if (!result) + { + return false; + } + + var codeStyleOptions = (IEnumerable)parameters[2]!; + foreach (var codeStyleOptionObj in codeStyleOptions) + { + var codeStyleOption = (IOption)codeStyleOptionObj!; + var option = options.GetOption(new OptionKey(codeStyleOption, codeStyleOption.IsPerLanguage ? compilation.Language : null)); + if (option is null) + { + continue; + } + + var notificationProperty = option.GetType().GetProperty("Notification"); + if (notificationProperty is null) + { + continue; + } + + var notification = notificationProperty.GetValue(option); + var reportDiagnosticValue = notification?.GetType().GetProperty("Severity")?.GetValue(notification); + if (reportDiagnosticValue is null) + { + continue; + } + + var codeStyleSeverity = ToSeverity((ReportDiagnostic)reportDiagnosticValue); + if (codeStyleSeverity > severity) + { + severity = codeStyleSeverity; + } + } + + return true; + } + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Analyzers/Interfaces/IAnalyzerInformationProvider.cs b/src/BuiltInTools/dotnet-format/Analyzers/Interfaces/IAnalyzerInformationProvider.cs new file mode 100644 index 000000000000..188cde35bd87 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Analyzers/Interfaces/IAnalyzerInformationProvider.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Analyzers +{ + internal interface IAnalyzerInformationProvider + { + DiagnosticSeverity GetSeverity(FormatOptions formatOptions); + + ImmutableDictionary GetAnalyzersAndFixers( + Workspace workspace, + Solution solution, + FormatOptions formatOptions, + ILogger logger); + } +} diff --git a/src/BuiltInTools/dotnet-format/Analyzers/Interfaces/IAnalyzerRunner.cs b/src/BuiltInTools/dotnet-format/Analyzers/Interfaces/IAnalyzerRunner.cs new file mode 100644 index 000000000000..78ff17ed3d25 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Analyzers/Interfaces/IAnalyzerRunner.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Analyzers +{ + internal interface IAnalyzerRunner + { + Task RunCodeAnalysisAsync( + CodeAnalysisResult result, + DiagnosticAnalyzer analyzers, + Project project, + ImmutableHashSet formattableDocumentPaths, + DiagnosticSeverity severity, + ImmutableHashSet fixableCompilerDiagnostics, + ILogger logger, + CancellationToken cancellationToken); + + Task RunCodeAnalysisAsync( + CodeAnalysisResult result, + ImmutableArray analyzers, + Project project, + ImmutableHashSet formattableDocumentPaths, + DiagnosticSeverity severity, + ImmutableHashSet fixableCompilerDiagnostics, + ILogger logger, + CancellationToken cancellationToken); + } +} diff --git a/src/BuiltInTools/dotnet-format/Analyzers/Interfaces/ICodeFixApplier.cs b/src/BuiltInTools/dotnet-format/Analyzers/Interfaces/ICodeFixApplier.cs new file mode 100644 index 000000000000..52a7acb13a6a --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Analyzers/Interfaces/ICodeFixApplier.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Analyzers +{ + internal interface ICodeFixApplier + { + Task ApplyCodeFixesAsync( + Solution solution, + CodeAnalysisResult result, + CodeFixProvider codefixes, + string diagnosticId, + ILogger logger, + CancellationToken cancellationToken); + } +} diff --git a/src/BuiltInTools/dotnet-format/Analyzers/SolutionCodeFixApplier.cs b/src/BuiltInTools/dotnet-format/Analyzers/SolutionCodeFixApplier.cs new file mode 100644 index 000000000000..737e6d1f6125 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Analyzers/SolutionCodeFixApplier.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; + +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Analyzers +{ + internal class SolutionCodeFixApplier : ICodeFixApplier + { + public async Task ApplyCodeFixesAsync( + Solution solution, + CodeAnalysisResult result, + CodeFixProvider codeFix, + string diagnosticId, + ILogger logger, + CancellationToken cancellationToken) + { + try + { + var fixAllProvider = codeFix.GetFixAllProvider(); + if (fixAllProvider?.GetSupportedFixAllScopes()?.Contains(FixAllScope.Solution) != true) + { + logger.LogWarning(Resources.Unable_to_fix_0_Code_fix_1_doesnt_support_Fix_All_in_Solution, diagnosticId, codeFix.GetType().Name); + return solution; + } + + var diagnostic = result.Diagnostics + .SelectMany(kvp => kvp.Value) + .Where(diagnostic => diagnostic.Location.SourceTree != null) + .FirstOrDefault(); + + if (diagnostic is null) + { + return solution; + } + + var document = solution.GetDocument(diagnostic.Location.SourceTree); + + if (document is null) + { + return solution; + } + + CodeAction? action = null; + var context = new CodeFixContext(document, diagnostic, + (a, _) => + { + if (action == null) + { + action = a; + } + }, + cancellationToken); + + await codeFix.RegisterCodeFixesAsync(context).ConfigureAwait(false); + + var fixAllContext = new FixAllContext( + document: document, + codeFixProvider: codeFix, + scope: FixAllScope.Solution, + codeActionEquivalenceKey: action?.EquivalenceKey!, // FixAllState supports null equivalence key. This should still be supported. + diagnosticIds: new[] { diagnosticId }, + fixAllDiagnosticProvider: new DiagnosticProvider(result), + cancellationToken: cancellationToken); + + var fixAllAction = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(false); + if (fixAllAction is null) + { + logger.LogWarning(Resources.Unable_to_fix_0_Code_fix_1_didnt_return_a_Fix_All_action, diagnosticId, codeFix.GetType().Name); + return solution; + } + + var operations = await fixAllAction.GetOperationsAsync(cancellationToken).ConfigureAwait(false); + var applyChangesOperation = operations.OfType().SingleOrDefault(); + if (applyChangesOperation is null) + { + logger.LogWarning(Resources.Unable_to_fix_0_Code_fix_1_returned_an_unexpected_operation, diagnosticId, codeFix.GetType().Name); + return solution; + } + + return applyChangesOperation.ChangedSolution; + } + catch (Exception ex) + { + logger.LogWarning(Resources.Failed_to_apply_code_fix_0_for_1_2, codeFix.GetType().Name, diagnosticId, ex.Message); + return solution; + } + } + + private class DiagnosticProvider : FixAllContext.DiagnosticProvider + { + private static Task> EmptyDignosticResult => Task.FromResult(Enumerable.Empty()); + private readonly IReadOnlyDictionary> _diagnosticsByProject; + + internal DiagnosticProvider(CodeAnalysisResult analysisResult) + { + _diagnosticsByProject = analysisResult.Diagnostics; + } + + public override Task> GetAllDiagnosticsAsync(Project project, CancellationToken cancellationToken) + { + return GetProjectDiagnosticsAsync(project, cancellationToken); + } + + public override async Task> GetDocumentDiagnosticsAsync(Document document, CancellationToken cancellationToken) + { + var projectDiagnostics = await GetProjectDiagnosticsAsync(document.Project, cancellationToken); + return projectDiagnostics.Where(diagnostic => diagnostic.Location.SourceTree?.FilePath == document.FilePath).ToImmutableArray(); + } + + public override Task> GetProjectDiagnosticsAsync(Project project, CancellationToken cancellationToken) + { + return _diagnosticsByProject.ContainsKey(project) + ? Task.FromResult>(_diagnosticsByProject[project]) + : EmptyDignosticResult; + } + } + } +} diff --git a/src/BuiltInTools/dotnet-format/CodeFormatter.cs b/src/BuiltInTools/dotnet-format/CodeFormatter.cs new file mode 100644 index 000000000000..3876d61bee51 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/CodeFormatter.cs @@ -0,0 +1,283 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Diagnostics; +using Microsoft.CodeAnalysis.Tools.Analyzers; +using Microsoft.CodeAnalysis.Tools.Formatters; +using Microsoft.CodeAnalysis.Tools.Utilities; +using Microsoft.CodeAnalysis.Tools.Workspaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools +{ + internal static class CodeFormatter + { + private static readonly ImmutableArray s_codeFormatters = ImmutableArray.Create( + new WhitespaceFormatter(), + new FinalNewlineFormatter(), + new EndOfLineFormatter(), + new CharsetFormatter(), + new OrganizeImportsFormatter(), + AnalyzerFormatter.CodeStyleFormatter, + AnalyzerFormatter.ThirdPartyFormatter); + + public static async Task FormatWorkspaceAsync( + FormatOptions formatOptions, + ILogger logger, + CancellationToken cancellationToken, + string? binaryLogPath = null) + { + var logWorkspaceWarnings = formatOptions.LogLevel == LogLevel.Trace; + + logger.LogInformation(string.Format(Resources.Formatting_code_files_in_workspace_0, formatOptions.WorkspaceFilePath)); + + logger.LogTrace(Resources.Loading_workspace); + + var workspaceStopwatch = Stopwatch.StartNew(); + + using var workspace = formatOptions.WorkspaceType == WorkspaceType.Folder + ? OpenFolderWorkspace(formatOptions.WorkspaceFilePath, formatOptions.FileMatcher) + : await OpenMSBuildWorkspaceAsync(formatOptions.WorkspaceFilePath, formatOptions.WorkspaceType, formatOptions.NoRestore, formatOptions.FixCategory != FixCategory.Whitespace, binaryLogPath, logWorkspaceWarnings, logger, cancellationToken).ConfigureAwait(false); + + if (workspace is null) + { + return new WorkspaceFormatResult(filesFormatted: 0, fileCount: 0, exitCode: 1); + } + + if (formatOptions.LogLevel <= LogLevel.Debug) + { + foreach (var project in workspace.CurrentSolution.Projects) + { + foreach (var configDocument in project.AnalyzerConfigDocuments) + { + logger.LogDebug(Resources.Project_0_is_using_configuration_from_1, project.Name, configDocument.FilePath); + } + } + } + + var loadWorkspaceMS = workspaceStopwatch.ElapsedMilliseconds; + logger.LogTrace(Resources.Complete_in_0_ms, loadWorkspaceMS); + + var projectPath = formatOptions.WorkspaceType == WorkspaceType.Project ? formatOptions.WorkspaceFilePath : string.Empty; + var solution = workspace.CurrentSolution; + + logger.LogTrace(Resources.Determining_formattable_files); + + var (fileCount, formatableFiles) = await DetermineFormattableFilesAsync( + solution, projectPath, formatOptions, logger, cancellationToken).ConfigureAwait(false); + + var determineFilesMS = workspaceStopwatch.ElapsedMilliseconds - loadWorkspaceMS; + logger.LogTrace(Resources.Complete_in_0_ms, determineFilesMS); + + logger.LogTrace(Resources.Running_formatters); + + var formattedFiles = new List(fileCount); + var formattedSolution = await RunCodeFormattersAsync( + workspace, solution, formatableFiles, formatOptions, logger, formattedFiles, cancellationToken).ConfigureAwait(false); + + var formatterRanMS = workspaceStopwatch.ElapsedMilliseconds - loadWorkspaceMS - determineFilesMS; + logger.LogTrace(Resources.Complete_in_0_ms, formatterRanMS); + + var documentIdsWithErrors = formattedFiles.Select(file => file.DocumentId).Distinct().ToImmutableArray(); + foreach (var documentId in documentIdsWithErrors) + { + var documentWithError = solution.GetDocument(documentId); + if (documentWithError is null) + { + documentWithError = await solution.GetSourceGeneratedDocumentAsync(documentId, cancellationToken).ConfigureAwait(false); + } + + logger.LogInformation(Resources.Formatted_code_file_0, documentWithError!.FilePath); + } + + var exitCode = 0; + + if (formatOptions.SaveFormattedFiles && !workspace.TryApplyChanges(formattedSolution)) + { + logger.LogError(Resources.Failed_to_save_formatting_changes); + exitCode = 1; + } + + if (exitCode == 0 && !string.IsNullOrWhiteSpace(formatOptions.ReportPath)) + { + ReportWriter.Write(formatOptions.ReportPath!, formattedFiles, logger); + } + + logger.LogDebug(Resources.Formatted_0_of_1_files, documentIdsWithErrors.Length, fileCount); + + logger.LogInformation(Resources.Format_complete_in_0_ms, workspaceStopwatch.ElapsedMilliseconds); + + return new WorkspaceFormatResult(documentIdsWithErrors.Length, fileCount, exitCode); + } + + private static Workspace OpenFolderWorkspace(string workspacePath, SourceFileMatcher fileMatcher) + { + var folderWorkspace = FolderWorkspace.Create(); + folderWorkspace.OpenFolder(workspacePath, fileMatcher); + return folderWorkspace; + } + + private static async Task OpenMSBuildWorkspaceAsync( + string solutionOrProjectPath, + WorkspaceType workspaceType, + bool noRestore, + bool requiresSemantics, + string? binaryLogPath, + bool logWorkspaceWarnings, + ILogger logger, + CancellationToken cancellationToken) + { + if (requiresSemantics && + !noRestore && + await DotNetHelper.PerformRestoreAsync(solutionOrProjectPath, logger) != 0) + { + throw new Exception("Restore operation failed."); + } + + return await MSBuildWorkspaceLoader.LoadAsync(solutionOrProjectPath, workspaceType, binaryLogPath, logWorkspaceWarnings, logger, cancellationToken); + } + + private static async Task RunCodeFormattersAsync( + Workspace workspace, + Solution solution, + ImmutableArray formattableDocuments, + FormatOptions formatOptions, + ILogger logger, + List formattedFiles, + CancellationToken cancellationToken) + { + var formattedSolution = solution; + + for (var index = 0; index < s_codeFormatters.Length; index++) + { + // Only run the formatter if it belongs to one of the categories being fixed. + if (!formatOptions.FixCategory.HasFlag(s_codeFormatters[index].Category)) + { + continue; + } + + formattedSolution = await s_codeFormatters[index].FormatAsync(workspace, formattedSolution, formattableDocuments, formatOptions, logger, formattedFiles, cancellationToken).ConfigureAwait(false); + } + + return formattedSolution; + } + + internal static async Task<(int, ImmutableArray)> DetermineFormattableFilesAsync( + Solution solution, + string projectPath, + FormatOptions formatOptions, + ILogger logger, + CancellationToken cancellationToken) + { + var totalFileCount = solution.Projects.Sum(project => project.DocumentIds.Count); + var projectFileCount = 0; + + var documentsCoveredByEditorConfig = ImmutableArray.CreateBuilder(totalFileCount); + var documentsNotCoveredByEditorConfig = ImmutableArray.CreateBuilder(totalFileCount); + var sourceGeneratedDocuments = ImmutableArray.CreateBuilder(); + + var addedFilePaths = new HashSet(totalFileCount); + + foreach (var project in solution.Projects) + { + if (project?.FilePath is null) + { + continue; + } + + // If a project is used as a workspace, then ignore other referenced projects. + if (!string.IsNullOrEmpty(projectPath) && !project.FilePath.Equals(projectPath, StringComparison.OrdinalIgnoreCase)) + { + logger.LogDebug(Resources.Skipping_referenced_project_0, project.Name); + continue; + } + + // Ignore unsupported project types. + if (project.Language != LanguageNames.CSharp && project.Language != LanguageNames.VisualBasic) + { + logger.LogWarning(Resources.Could_not_format_0_Format_currently_supports_only_CSharp_and_Visual_Basic_projects, project.FilePath); + continue; + } + + projectFileCount += project.DocumentIds.Count; + + foreach (var document in project.Documents) + { + // If we've already added this document, either via a link or multi-targeted framework, then ignore. + if (document?.FilePath is null || + addedFilePaths.Contains(document.FilePath)) + { + continue; + } + + addedFilePaths.Add(document.FilePath); + + var isFileIncluded = formatOptions.WorkspaceType == WorkspaceType.Folder || + (formatOptions.FileMatcher.HasMatches(document.FilePath) && File.Exists(document.FilePath)); + if (!isFileIncluded || !document.SupportsSyntaxTree) + { + continue; + } + + var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + if (syntaxTree is null) + { + throw new Exception($"Unable to get a syntax tree for '{document.Name}'"); + } + + if (await GeneratedCodeUtilities.IsGeneratedCodeAsync(syntaxTree, cancellationToken).ConfigureAwait(false)) + { + if (!formatOptions.IncludeGeneratedFiles) + { + continue; + } + else + { + Debug.WriteLine($"Including generated file '{document.FilePath}'."); + } + } + + // Track files covered by an editorconfig separately from those not covered. + var analyzerConfigOptions = document.Project.AnalyzerOptions.AnalyzerConfigOptionsProvider.GetOptions(syntaxTree); + if (analyzerConfigOptions != null) + { + if (formatOptions.IncludeGeneratedFiles || + GeneratedCodeUtilities.GetIsGeneratedCodeFromOptions(analyzerConfigOptions) != true) + { + documentsCoveredByEditorConfig.Add(document.Id); + } + } + else + { + documentsNotCoveredByEditorConfig.Add(document.Id); + } + } + + if (formatOptions.IncludeGeneratedFiles) + { + var generatedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); + foreach (var generatedDocument in generatedDocuments) + { + Debug.WriteLine($"Including source generated file '{generatedDocument.FilePath}'."); + sourceGeneratedDocuments.Add(generatedDocument.Id); + } + } + } + + // Initially we would format all documents in a workspace, even if some files weren't covered by an + // .editorconfig and would have defaults applied. This behavior was an early requested change since + // users were surprised to have files not specified by the .editorconfig modified. The assumption is + // that users without an .editorconfig still wanted formatting (they did run a formatter after all), + // so we run on all files with defaults. + + // If no files are covered by an editorconfig, then return them all. Otherwise only return + // files that are covered by an editorconfig. + var formattableDocuments = documentsCoveredByEditorConfig.Count == 0 + ? documentsNotCoveredByEditorConfig + : documentsCoveredByEditorConfig; + + formattableDocuments.AddRange(sourceGeneratedDocuments); + return (projectFileCount + sourceGeneratedDocuments.Count, formattableDocuments.ToImmutable()); + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Commands/FormatAnalyzersCommand.cs b/src/BuiltInTools/dotnet-format/Commands/FormatAnalyzersCommand.cs new file mode 100644 index 000000000000..3a601096addf --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Commands/FormatAnalyzersCommand.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.CommandLine; +using System.CommandLine.Invocation; +using System.CommandLine.IO; +using Microsoft.Extensions.Logging; +using static Microsoft.CodeAnalysis.Tools.FormatCommandCommon; + +namespace Microsoft.CodeAnalysis.Tools.Commands +{ + internal static class FormatAnalyzersCommand + { + private static readonly FormatAnalyzersHandler s_analyzerHandler = new(); + + internal static CliCommand GetCommand() + { + var command = new CliCommand("analyzers", Resources.Run_3rd_party_analyzers__and_apply_fixes) + { + DiagnosticsOption, + ExcludeDiagnosticsOption, + SeverityOption, + }; + command.AddCommonOptions(); + command.Action = s_analyzerHandler; + return command; + } + + private class FormatAnalyzersHandler : AsynchronousCliAction + { + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken cancellationToken) + { + var formatOptions = parseResult.ParseVerbosityOption(FormatOptions.Instance); + var logger = new SystemConsole().SetupLogging(minimalLogLevel: formatOptions.LogLevel, minimalErrorLevel: LogLevel.Warning); + formatOptions = parseResult.ParseCommonOptions(formatOptions, logger); + formatOptions = parseResult.ParseWorkspaceOptions(formatOptions); + + if (parseResult.GetResult(SeverityOption) is not null && + parseResult.GetValue(SeverityOption) is string { Length: > 0 } analyzerSeverity) + { + formatOptions = formatOptions with { AnalyzerSeverity = GetSeverity(analyzerSeverity) }; + } + + if (parseResult.GetResult(DiagnosticsOption) is not null && + parseResult.GetValue(DiagnosticsOption) is string[] { Length: > 0 } diagnostics) + { + formatOptions = formatOptions with { Diagnostics = diagnostics.ToImmutableHashSet() }; + } + + if (parseResult.GetResult(ExcludeDiagnosticsOption) is not null && + parseResult.GetValue(ExcludeDiagnosticsOption) is string[] { Length: > 0 } excludeDiagnostics) + { + formatOptions = formatOptions with { ExcludeDiagnostics = excludeDiagnostics.ToImmutableHashSet() }; + } + + formatOptions = formatOptions with { FixCategory = FixCategory.Analyzers }; + + return await FormatAsync(formatOptions, logger, cancellationToken).ConfigureAwait(false); + } + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Commands/FormatCommandCommon.cs b/src/BuiltInTools/dotnet-format/Commands/FormatCommandCommon.cs new file mode 100644 index 000000000000..20486f7baa0e --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Commands/FormatCommandCommon.cs @@ -0,0 +1,409 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.CommandLine; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Microsoft.CodeAnalysis.Tools.Logging; +using Microsoft.CodeAnalysis.Tools.Utilities; +using Microsoft.CodeAnalysis.Tools.Workspaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools +{ + internal static class FormatCommandCommon + { + internal const int UnhandledExceptionExitCode = 1; + internal const int CheckFailedExitCode = 2; + internal const int UnableToLocateMSBuildExitCode = 3; + internal const int UnableToLocateDotNetCliExitCode = 4; + + private static string[] VerbosityLevels => new[] { "q", "quiet", "m", "minimal", "n", "normal", "d", "detailed", "diag", "diagnostic" }; + private static string[] SeverityLevels => new[] { "info", "warn", "error" }; + + public static readonly CliArgument SlnOrProjectArgument = new CliArgument(Resources.SolutionOrProjectArgumentName) + { + Description = Resources.SolutionOrProjectArgumentDescription, + Arity = ArgumentArity.ZeroOrOne + }.DefaultToCurrentDirectory(); + + internal static readonly CliOption FolderOption = new("--folder") + { + Description = Resources.Whether_to_treat_the_workspace_argument_as_a_simple_folder_of_files, + }; + internal static readonly CliOption NoRestoreOption = new("--no-restore") + { + Description = Resources.Doesnt_execute_an_implicit_restore_before_formatting, + }; + internal static readonly CliOption VerifyNoChanges = new("--verify-no-changes") + { + Description = Resources.Verify_no_formatting_changes_would_be_performed_Terminates_with_a_non_zero_exit_code_if_any_files_would_have_been_formatted, + }; + internal static readonly CliOption DiagnosticsOption = new("--diagnostics") + { + AllowMultipleArgumentsPerToken = true, + DefaultValueFactory = _ => Array.Empty(), + Description = Resources.A_space_separated_list_of_diagnostic_ids_to_use_as_a_filter_when_fixing_code_style_or_3rd_party_issues, + }; + internal static readonly CliOption ExcludeDiagnosticsOption = new("--exclude-diagnostics") + { + AllowMultipleArgumentsPerToken = true, + DefaultValueFactory = _ => Array.Empty(), + Description = Resources.A_space_separated_list_of_diagnostic_ids_to_ignore_when_fixing_code_style_or_3rd_party_issues, + }; + internal static readonly CliOption SeverityOption = new CliOption("--severity") + { + Description = Resources.The_severity_of_diagnostics_to_fix_Allowed_values_are_info_warn_and_error, + }; + internal static readonly CliOption IncludeOption = new("--include") + { + AllowMultipleArgumentsPerToken = true, + DefaultValueFactory = _ => Array.Empty(), + Description = Resources.A_list_of_relative_file_or_folder_paths_to_include_in_formatting_All_files_are_formatted_if_empty, + }; + internal static readonly CliOption ExcludeOption = new("--exclude") + { + AllowMultipleArgumentsPerToken = true, + DefaultValueFactory = _ => Array.Empty(), + Description = Resources.A_list_of_relative_file_or_folder_paths_to_exclude_from_formatting, + }; + internal static readonly CliOption IncludeGeneratedOption = new("--include-generated") + { + Description = Resources.Format_files_generated_by_the_SDK, + }; + internal static readonly CliOption VerbosityOption = new CliOption("--verbosity", "-v") + { + Description = Resources.Set_the_verbosity_level_Allowed_values_are_quiet_minimal_normal_detailed_and_diagnostic, + }; + internal static readonly CliOption BinarylogOption = new CliOption("--binarylog") + { + HelpName = "binary-log-path", + Arity = ArgumentArity.ZeroOrOne, + Description = Resources.Log_all_project_or_solution_load_information_to_a_binary_log_file, + }; + internal static readonly CliOption ReportOption = new CliOption("--report") + { + HelpName = "report-path", + Arity = ArgumentArity.ZeroOrOne, + Description = Resources.Accepts_a_file_path_which_if_provided_will_produce_a_json_report_in_the_given_directory, + }; + + static FormatCommandCommon() + { + SeverityOption.AcceptOnlyFromAmong(SeverityLevels); + VerbosityOption.AcceptOnlyFromAmong(VerbosityLevels); + BinarylogOption.AcceptLegalFilePathsOnly(); + ReportOption.AcceptLegalFilePathsOnly(); + } + + internal static async Task FormatAsync(FormatOptions formatOptions, ILogger logger, CancellationToken cancellationToken) + { + if (formatOptions.WorkspaceType != WorkspaceType.Folder) + { + var runtimeVersion = GetRuntimeVersion(); + logger.LogDebug(Resources.The_dotnet_runtime_version_is_0, runtimeVersion); + + if (!TryGetDotNetCliVersion(out var dotnetVersion)) + { + logger.LogError(Resources.Unable_to_locate_dotnet_CLI_Ensure_that_it_is_on_the_PATH); + return UnableToLocateDotNetCliExitCode; + } + + logger.LogTrace(Resources.The_dotnet_CLI_version_is_0, dotnetVersion); + + if (!TryLoadMSBuild(out var msBuildPath)) + { + logger.LogError(Resources.Unable_to_locate_MSBuild_Ensure_the_NET_SDK_was_installed_with_the_official_installer); + return UnableToLocateMSBuildExitCode; + } + + logger.LogTrace(Resources.Using_msbuildexe_located_in_0, msBuildPath); + } + + var formatResult = await CodeFormatter.FormatWorkspaceAsync( + formatOptions, + logger, + cancellationToken, + binaryLogPath: formatOptions.BinaryLogPath).ConfigureAwait(false); + return formatResult.GetExitCode(formatOptions.ChangesAreErrors); + } + + public static void AddCommonOptions(this CliCommand command) + { + command.Arguments.Add(SlnOrProjectArgument); + command.Options.Add(NoRestoreOption); + command.Options.Add(VerifyNoChanges); + command.Options.Add(IncludeOption); + command.Options.Add(ExcludeOption); + command.Options.Add(IncludeGeneratedOption); + command.Options.Add(VerbosityOption); + command.Options.Add(BinarylogOption); + command.Options.Add(ReportOption); + } + + public static CliArgument DefaultToCurrentDirectory(this CliArgument arg) + { + arg.DefaultValueFactory = _ => EnsureTrailingSlash(Directory.GetCurrentDirectory()); + return arg; + } + + public static ILogger SetupLogging(this IConsole console, LogLevel minimalLogLevel, LogLevel minimalErrorLevel) + { + var loggerFactory = new LoggerFactory() + .AddSimpleConsole(console, minimalLogLevel, minimalErrorLevel); + var logger = loggerFactory.CreateLogger(); + return logger; + } + + public static int GetExitCode(this WorkspaceFormatResult formatResult, bool check) + { + if (!check) + { + return formatResult.ExitCode; + } + + return formatResult.FilesFormatted == 0 ? 0 : CheckFailedExitCode; + } + + public static FormatOptions ParseVerbosityOption(this ParseResult parseResult, FormatOptions formatOptions) + { + if (parseResult.GetResult(VerbosityOption) is not null && + parseResult.GetValue(VerbosityOption) is string { Length: > 0 } verbosity) + { + formatOptions = formatOptions with { LogLevel = GetLogLevel(verbosity) }; + } + else + { + formatOptions = formatOptions with { LogLevel = LogLevel.Warning }; + } + + return formatOptions; + } + + public static FormatOptions ParseCommonOptions(this ParseResult parseResult, FormatOptions formatOptions, ILogger logger) + { + if (parseResult.GetResult(NoRestoreOption) is not null) + { + formatOptions = formatOptions with { NoRestore = true }; + } + + if (parseResult.GetResult(VerifyNoChanges) is not null) + { + formatOptions = formatOptions with { ChangesAreErrors = true }; + formatOptions = formatOptions with { SaveFormattedFiles = false }; + } + + if (parseResult.GetResult(IncludeGeneratedOption) is not null) + { + formatOptions = formatOptions with { IncludeGeneratedFiles = true }; + } + + if (parseResult.GetResult(IncludeOption) is not null || parseResult.GetResult(ExcludeOption) is not null) + { + var fileToInclude = parseResult.GetValue(IncludeOption) ?? Array.Empty(); + var fileToExclude = parseResult.GetValue(ExcludeOption) ?? Array.Empty(); + HandleStandardInput(logger, ref fileToInclude, ref fileToExclude); + formatOptions = formatOptions with { FileMatcher = SourceFileMatcher.CreateMatcher(fileToInclude, fileToExclude) }; + } + + if (parseResult.GetResult(ReportOption) is not null) + { + formatOptions = formatOptions with { ReportPath = string.Empty }; + + if (parseResult.GetValue(ReportOption) is string { Length: > 0 } reportPath) + { + formatOptions = formatOptions with { ReportPath = reportPath }; + } + } + + if (parseResult.GetResult(BinarylogOption) is not null) + { + formatOptions = formatOptions with { BinaryLogPath = "format.binlog" }; + + if (parseResult.GetValue(BinarylogOption) is string { Length: > 0 } binaryLogPath) + { + formatOptions = Path.GetExtension(binaryLogPath)?.Equals(".binlog") == false + ? (formatOptions with { BinaryLogPath = Path.ChangeExtension(binaryLogPath, ".binlog") }) + : (formatOptions with { BinaryLogPath = binaryLogPath }); + } + } + + return formatOptions; + + static void HandleStandardInput(ILogger logger, ref string[] include, ref string[] exclude) + { + string[] standardInputKeywords = { "/dev/stdin", "-" }; + const int CheckFailedExitCode = 2; + + var isStandardMarkerUsed = false; + if (include.Length == 1 && standardInputKeywords.Contains(include[0])) + { + if (TryReadFromStandardInput(ref include)) + { + isStandardMarkerUsed = true; + } + } + + if (exclude.Length == 1 && standardInputKeywords.Contains(exclude[0])) + { + if (isStandardMarkerUsed) + { + logger.LogCritical(Resources.Standard_input_used_multiple_times); + Environment.Exit(CheckFailedExitCode); + } + + TryReadFromStandardInput(ref exclude); + } + + static bool TryReadFromStandardInput(ref string[] subject) + { + if (!Console.IsInputRedirected) + { + return false; // pass + } + + // reset the subject array + Array.Clear(subject, 0, subject.Length); + Array.Resize(ref subject, 0); + + Console.InputEncoding = Encoding.UTF8; + using var reader = new StreamReader(Console.OpenStandardInput(8192)); + Console.SetIn(reader); + + for (var i = 0; Console.In.Peek() != -1; ++i) + { + var line = Console.In.ReadLine(); + if (line is null) + { + continue; + } + + Array.Resize(ref subject, subject.Length + 1); + subject[i] = line; + } + + return true; + } + } + } + + internal static LogLevel GetLogLevel(string? verbosity) + { + return verbosity switch + { + "q" or "quiet" => LogLevel.Error, + "m" or "minimal" => LogLevel.Warning, + "n" or "normal" => LogLevel.Information, + "d" or "detailed" => LogLevel.Debug, + "diag" or "diagnostic" => LogLevel.Trace, + _ => LogLevel.Information, + }; + } + + internal static DiagnosticSeverity GetSeverity(string? severity) + { + return severity?.ToLowerInvariant() switch + { + "" => DiagnosticSeverity.Error, + FixSeverity.Error => DiagnosticSeverity.Error, + FixSeverity.Warn => DiagnosticSeverity.Warning, + FixSeverity.Info => DiagnosticSeverity.Info, + _ => throw new ArgumentOutOfRangeException(nameof(severity)), + }; + } + + public static FormatOptions ParseWorkspaceOptions(this ParseResult parseResult, FormatOptions formatOptions) + { + var currentDirectory = Environment.CurrentDirectory; + + if (parseResult.GetValue(SlnOrProjectArgument) is string { Length: > 0 } slnOrProject) + { + if (parseResult.GetResult(FolderOption) is not null) + { + formatOptions = formatOptions with { WorkspaceFilePath = slnOrProject }; + formatOptions = formatOptions with { WorkspaceType = WorkspaceType.Folder }; + return formatOptions; + } + + var (isSolution, workspaceFilePath) = MSBuildWorkspaceFinder.FindWorkspace(currentDirectory, slnOrProject); + formatOptions = formatOptions with { WorkspaceFilePath = workspaceFilePath }; + formatOptions = formatOptions with { WorkspaceType = isSolution ? WorkspaceType.Solution : WorkspaceType.Project }; + + // To ensure we get the version of MSBuild packaged with the dotnet SDK used by the + // workspace, use its directory as our working directory which will take into account + // a global.json if present. + var workspaceDirectory = Path.GetDirectoryName(workspaceFilePath); + if (workspaceDirectory is null) + { + throw new Exception($"Unable to find folder at '{workspaceFilePath}'"); + } + } + + return formatOptions; + } + + private static string EnsureTrailingSlash(string path) + => !string.IsNullOrEmpty(path) && + path[^1] != Path.DirectorySeparatorChar + ? path + Path.DirectorySeparatorChar + : path; + + internal static string? GetVersion() + { + return Assembly.GetExecutingAssembly() + .GetCustomAttribute() + ?.InformationalVersion; + } + + internal static bool TryGetDotNetCliVersion([NotNullWhen(returnValue: true)] out string? dotnetVersion) + { + try + { + var processInfo = ProcessRunner.CreateProcess("dotnet", "--version", captureOutput: true, displayWindow: false); + var versionResult = processInfo.Result.GetAwaiter().GetResult(); + + dotnetVersion = versionResult.OutputLines[0].Trim(); + return true; + } + catch + { + dotnetVersion = null; + return false; + } + } + + internal static bool TryLoadMSBuild([NotNullWhen(returnValue: true)] out string? msBuildPath) + { + try + { + // Get the global.json pinned SDK or latest instance. + var msBuildInstance = Build.Locator.MSBuildLocator.QueryVisualStudioInstances() + .Where(instance => instance.Version.Major >= 6) + .FirstOrDefault(); + if (msBuildInstance is null) + { + msBuildPath = null; + return false; + } + + msBuildPath = Path.EndsInDirectorySeparator(msBuildInstance.MSBuildPath) + ? msBuildInstance.MSBuildPath + : msBuildInstance.MSBuildPath + Path.DirectorySeparatorChar; + + Build.Locator.MSBuildLocator.RegisterMSBuildPath(msBuildPath); + return true; + } + catch + { + msBuildPath = null; + return false; + } + } + + internal static string GetRuntimeVersion() + { + var pathParts = typeof(string).Assembly.Location.Split('\\', '/'); + var netCoreAppIndex = Array.IndexOf(pathParts, "Microsoft.NETCore.App"); + return pathParts[netCoreAppIndex + 1]; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Commands/FormatStyleCommand.cs b/src/BuiltInTools/dotnet-format/Commands/FormatStyleCommand.cs new file mode 100644 index 000000000000..d7aaa6519774 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Commands/FormatStyleCommand.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.CommandLine; +using System.CommandLine.Invocation; +using System.CommandLine.IO; +using Microsoft.Extensions.Logging; +using static Microsoft.CodeAnalysis.Tools.FormatCommandCommon; + +namespace Microsoft.CodeAnalysis.Tools.Commands +{ + internal static class FormatStyleCommand + { + private static readonly FormatStyleHandler s_styleHandler = new(); + + internal static CliCommand GetCommand() + { + var command = new CliCommand("style", Resources.Run_code_style_analyzers_and_apply_fixes) + { + DiagnosticsOption, + ExcludeDiagnosticsOption, + SeverityOption, + }; + command.AddCommonOptions(); + command.Action = s_styleHandler; + return command; + } + + private class FormatStyleHandler : AsynchronousCliAction + { + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken cancellationToken) + { + var formatOptions = parseResult.ParseVerbosityOption(FormatOptions.Instance); + var logger = new SystemConsole().SetupLogging(minimalLogLevel: formatOptions.LogLevel, minimalErrorLevel: LogLevel.Warning); + formatOptions = parseResult.ParseCommonOptions(formatOptions, logger); + formatOptions = parseResult.ParseWorkspaceOptions(formatOptions); + + if (parseResult.GetResult(SeverityOption) is not null && + parseResult.GetValue(SeverityOption) is string { Length: > 0 } styleSeverity) + { + formatOptions = formatOptions with { CodeStyleSeverity = GetSeverity(styleSeverity) }; + } + + if (parseResult.GetResult(DiagnosticsOption) is not null && + parseResult.GetValue(DiagnosticsOption) is string[] { Length: > 0 } diagnostics) + { + formatOptions = formatOptions with { Diagnostics = diagnostics.ToImmutableHashSet() }; + } + + if (parseResult.GetResult(ExcludeDiagnosticsOption) is not null && + parseResult.GetValue(ExcludeDiagnosticsOption) is string[] { Length: > 0 } excludeDiagnostics) + { + formatOptions = formatOptions with { ExcludeDiagnostics = excludeDiagnostics.ToImmutableHashSet() }; + } + + formatOptions = formatOptions with { FixCategory = FixCategory.CodeStyle }; + + return await FormatAsync(formatOptions, logger, cancellationToken).ConfigureAwait(false); + } + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Commands/FormatWhitespaceCommand.cs b/src/BuiltInTools/dotnet-format/Commands/FormatWhitespaceCommand.cs new file mode 100644 index 000000000000..3c28fe4c5474 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Commands/FormatWhitespaceCommand.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.CommandLine; +using System.CommandLine.Invocation; +using System.CommandLine.IO; +using System.CommandLine.Parsing; +using Microsoft.Extensions.Logging; +using static Microsoft.CodeAnalysis.Tools.FormatCommandCommon; + +namespace Microsoft.CodeAnalysis.Tools.Commands +{ + internal static class FormatWhitespaceCommand + { + // This delegate should be kept in Sync with the FormatCommand options and argument names + // so that values bind correctly. + internal delegate Task Handler( + bool folder, + string? workspace, + bool noRestore, + bool check, + string[] include, + string[] exclude, + bool includeGenerated, + string? verbosity, + string? binarylog, + string? report, + IConsole console); + + private static readonly FormatWhitespaceHandler s_formattingHandler = new(); + + internal static CliCommand GetCommand() + { + var command = new CliCommand("whitespace", Resources.Run_whitespace_formatting) + { + FolderOption + }; + command.AddCommonOptions(); + command.Validators.Add(EnsureFolderNotSpecifiedWithNoRestore); + command.Validators.Add(EnsureFolderNotSpecifiedWhenLoggingBinlog); + command.Action = s_formattingHandler; + return command; + } + + internal static void EnsureFolderNotSpecifiedWithNoRestore(CommandResult symbolResult) + { + var folder = symbolResult.GetValue(FolderOption); + var noRestore = symbolResult.GetResult(NoRestoreOption); + if (folder && noRestore != null) + { + symbolResult.AddError(Resources.Cannot_specify_the_folder_option_with_no_restore); + } + } + + internal static void EnsureFolderNotSpecifiedWhenLoggingBinlog(CommandResult symbolResult) + { + var folder = symbolResult.GetValue(FolderOption); + var binarylog = symbolResult.GetResult(BinarylogOption); + if (folder && binarylog is not null && !binarylog.Implicit) + { + symbolResult.AddError(Resources.Cannot_specify_the_folder_option_when_writing_a_binary_log); + } + } + + private class FormatWhitespaceHandler : AsynchronousCliAction + { + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken cancellationToken) + { + var formatOptions = parseResult.ParseVerbosityOption(FormatOptions.Instance); + var logger = new SystemConsole().SetupLogging(minimalLogLevel: formatOptions.LogLevel, minimalErrorLevel: LogLevel.Warning); + formatOptions = parseResult.ParseCommonOptions(formatOptions, logger); + formatOptions = parseResult.ParseWorkspaceOptions(formatOptions); + + formatOptions = formatOptions with { FixCategory = FixCategory.Whitespace }; + + return await FormatAsync(formatOptions, logger, cancellationToken).ConfigureAwait(false); + } + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Commands/RootFormatCommand.cs b/src/BuiltInTools/dotnet-format/Commands/RootFormatCommand.cs new file mode 100644 index 000000000000..55947beed4ad --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Commands/RootFormatCommand.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.CommandLine; +using System.CommandLine.Invocation; +using System.CommandLine.IO; +using Microsoft.Extensions.Logging; +using static Microsoft.CodeAnalysis.Tools.FormatCommandCommon; + +namespace Microsoft.CodeAnalysis.Tools.Commands +{ + internal static class RootFormatCommand + { + private static readonly FormatCommandDefaultHandler s_formatCommandHandler = new(); + + public static CliRootCommand GetCommand() + { + var formatCommand = new CliRootCommand(Resources.Formats_code_to_match_editorconfig_settings) + { + FormatWhitespaceCommand.GetCommand(), + FormatStyleCommand.GetCommand(), + FormatAnalyzersCommand.GetCommand(), + DiagnosticsOption, + ExcludeDiagnosticsOption, + SeverityOption, + }; + formatCommand.AddCommonOptions(); + formatCommand.Action = s_formatCommandHandler; + return formatCommand; + } + + private class FormatCommandDefaultHandler : AsynchronousCliAction + { + public override async Task InvokeAsync(ParseResult parseResult, CancellationToken cancellationToken) + { + var formatOptions = parseResult.ParseVerbosityOption(FormatOptions.Instance); + var logger = new SystemConsole().SetupLogging(minimalLogLevel: formatOptions.LogLevel, minimalErrorLevel: LogLevel.Warning); + formatOptions = parseResult.ParseCommonOptions(formatOptions, logger); + formatOptions = parseResult.ParseWorkspaceOptions(formatOptions); + + if (parseResult.GetResult(SeverityOption) is not null && + parseResult.GetValue(SeverityOption) is string { Length: > 0 } defaultSeverity) + { + formatOptions = formatOptions with { AnalyzerSeverity = GetSeverity(defaultSeverity) }; + formatOptions = formatOptions with { CodeStyleSeverity = GetSeverity(defaultSeverity) }; + } + + if (parseResult.GetResult(DiagnosticsOption) is not null && + parseResult.GetValue(DiagnosticsOption) is string[] { Length: > 0 } diagnostics) + { + formatOptions = formatOptions with { Diagnostics = diagnostics.ToImmutableHashSet() }; + } + + if (parseResult.GetResult(ExcludeDiagnosticsOption) is not null && + parseResult.GetValue(ExcludeDiagnosticsOption) is string[] { Length: > 0 } excludeDiagnostics) + { + formatOptions = formatOptions with { ExcludeDiagnostics = excludeDiagnostics.ToImmutableHashSet() }; + } + + formatOptions = formatOptions with { FixCategory = FixCategory.Whitespace | FixCategory.CodeStyle | FixCategory.Analyzers }; + + return await FormatAsync(formatOptions, logger, cancellationToken).ConfigureAwait(false); + } + } + } +} diff --git a/src/BuiltInTools/dotnet-format/FileChange.cs b/src/BuiltInTools/dotnet-format/FileChange.cs new file mode 100644 index 000000000000..576223fcd022 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/FileChange.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Tools +{ + public class FileChange + { + public int LineNumber { get; } + + public int CharNumber { get; } + + public string DiagnosticId { get; } + + public string FormatDescription { get; } + + public FileChange(LinePosition changePosition, string diagnosticId, string formatDescription) + { + // LinePosition is zero based so we need to increment to report numbers people expect. + LineNumber = changePosition.Line + 1; + CharNumber = changePosition.Character + 1; + DiagnosticId = diagnosticId; + FormatDescription = formatDescription; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/FixCategory.cs b/src/BuiltInTools/dotnet-format/FixCategory.cs new file mode 100644 index 000000000000..ce8ea84d9d8b --- /dev/null +++ b/src/BuiltInTools/dotnet-format/FixCategory.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Tools +{ + [Flags] + internal enum FixCategory + { + None = 0, + Whitespace = 1, + CodeStyle = 2, + Analyzers = 4 + } +} diff --git a/src/BuiltInTools/dotnet-format/FixSeverity.cs b/src/BuiltInTools/dotnet-format/FixSeverity.cs new file mode 100644 index 000000000000..47b719bda074 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/FixSeverity.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Tools +{ + public static class FixSeverity + { + public const string Error = "error"; + public const string Warn = "warn"; + public const string Info = "info"; + } +} diff --git a/src/BuiltInTools/dotnet-format/FormatOptions.cs b/src/BuiltInTools/dotnet-format/FormatOptions.cs new file mode 100644 index 000000000000..552f43a6f003 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/FormatOptions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Tools.Utilities; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools +{ + internal record FormatOptions( + string WorkspaceFilePath, + WorkspaceType WorkspaceType, + bool NoRestore, + LogLevel LogLevel, + FixCategory FixCategory, + DiagnosticSeverity CodeStyleSeverity, + DiagnosticSeverity AnalyzerSeverity, + ImmutableHashSet Diagnostics, + ImmutableHashSet ExcludeDiagnostics, + bool SaveFormattedFiles, + bool ChangesAreErrors, + SourceFileMatcher FileMatcher, + string? ReportPath, + string? BinaryLogPath, + bool IncludeGeneratedFiles) + { + public static FormatOptions Instance = new( + WorkspaceFilePath: null!, // must be supplied + WorkspaceType: default, // must be supplied + NoRestore: false, + LogLevel: LogLevel.Warning, + FixCategory: default, // must be supplied + CodeStyleSeverity: DiagnosticSeverity.Warning, + AnalyzerSeverity: DiagnosticSeverity.Warning, + Diagnostics: ImmutableHashSet.Empty, + ExcludeDiagnostics: ImmutableHashSet.Empty, + SaveFormattedFiles: true, + ChangesAreErrors: false, + FileMatcher: SourceFileMatcher.CreateMatcher(Array.Empty(), Array.Empty()), + ReportPath: null, + BinaryLogPath: null, + IncludeGeneratedFiles: false); + } +} diff --git a/src/BuiltInTools/dotnet-format/FormattedFile.cs b/src/BuiltInTools/dotnet-format/FormattedFile.cs new file mode 100644 index 000000000000..931c73edcbfc --- /dev/null +++ b/src/BuiltInTools/dotnet-format/FormattedFile.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Tools +{ + public class FormattedFile + { + public DocumentId DocumentId { get; } + + public string FileName { get; } + + public string? FilePath { get; } + + public IEnumerable FileChanges { get; } + + public FormattedFile(Document document, IEnumerable fileChanges) + { + DocumentId = document.Id; + FileName = document.Name; + FilePath = document.FilePath; + FileChanges = fileChanges; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Formatters/CharsetFormatter.cs b/src/BuiltInTools/dotnet-format/Formatters/CharsetFormatter.cs new file mode 100644 index 000000000000..b2aa5af51847 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Formatters/CharsetFormatter.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Formatters +{ + internal sealed class CharsetFormatter : DocumentFormatter + { + protected override string FormatWarningDescription => Resources.Fix_file_encoding; + + private static Encoding Utf8 => new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + private static Encoding Latin1 => Encoding.GetEncoding("iso-8859-1"); + + public override string Name => "CHARSET"; + public override FixCategory Category => FixCategory.Whitespace; + + internal override Task FormatFileAsync( + Document document, + SourceText sourceText, + OptionSet optionSet, + AnalyzerConfigOptions analyzerConfigOptions, + FormatOptions formatOptions, + ILogger logger, + CancellationToken cancellationToken) + { + return Task.Run(() => + { + if (!TryGetCharset(analyzerConfigOptions, out var encoding) + || sourceText.Encoding?.Equals(encoding) == true + || IsEncodingEquivalent(sourceText, encoding)) + { + return sourceText; + } + + return SourceText.From(sourceText.ToString(), encoding, sourceText.ChecksumAlgorithm); + }); + } + + private static bool IsEncodingEquivalent(SourceText sourceText, Encoding encoding) + { + if (sourceText.Encoding is null) + { + throw new System.Exception($"source text did not have an identifiable encoding"); + } + + var text = sourceText.ToString(); + var originalBytes = GetEncodedBytes(text, sourceText.Encoding); + var encodedBytes = GetEncodedBytes(text, encoding); + + return originalBytes.Length == encodedBytes.Length + && originalBytes.SequenceEqual(encodedBytes); + } + + private static byte[] GetEncodedBytes(string text, Encoding encoding) + { + // Start with a large initial capacity, double the character count with additional space for the BOM + using var stream = new MemoryStream(text.Length * 2 + 3); + using var streamWriter = new StreamWriter(stream, encoding); + streamWriter.Write(text); + streamWriter.Flush(); + return stream.ToArray(); + } + + private static bool TryGetCharset(AnalyzerConfigOptions analyzerConfigOptions, [NotNullWhen(true)] out Encoding? encoding) + { + if (analyzerConfigOptions != null && + analyzerConfigOptions.TryGetValue("charset", out var charsetOption)) + { + encoding = GetCharset(charsetOption); + return true; + } + + encoding = null; + return false; + } + + public static Encoding GetCharset(string charsetOption) + { + return charsetOption switch + { + "latin1" => Latin1, + "utf-8-bom" => Encoding.UTF8,// UTF-8 with BOM Marker + "utf-16be" => Encoding.BigEndianUnicode,// Big Endian with BOM Marker + "utf-16le" => Encoding.Unicode,// Little Endian with BOM Marker + _ => Utf8, + }; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Formatters/DocumentFormatter.cs b/src/BuiltInTools/dotnet-format/Formatters/DocumentFormatter.cs new file mode 100644 index 000000000000..465d7bf033ed --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Formatters/DocumentFormatter.cs @@ -0,0 +1,222 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Formatters +{ + /// + /// Base class for code formatters that work against a single document at a time. + /// + internal abstract class DocumentFormatter : ICodeFormatter + { + protected abstract string FormatWarningDescription { get; } + + /// + /// Gets the fix name to use when logging. + /// + public abstract string Name { get; } + + /// + /// Gets the fix category this formatter belongs to. + /// + public abstract FixCategory Category { get; } + + /// + /// Applies formatting and returns a formatted + /// + public async Task FormatAsync( + Workspace workspace, + Solution solution, + ImmutableArray formattableDocuments, + FormatOptions formatOptions, + ILogger logger, + List formattedFiles, + CancellationToken cancellationToken) + { + var formattedDocuments = FormatFiles(solution, formattableDocuments, formatOptions, logger, cancellationToken); + return await ApplyFileChangesAsync(solution, formattedDocuments, formatOptions, logger, formattedFiles, cancellationToken).ConfigureAwait(false); + } + + /// + /// Applies formatting and returns the changed for a . + /// + internal abstract Task FormatFileAsync( + Document document, + SourceText sourceText, + OptionSet optionSet, + AnalyzerConfigOptions analyzerConfigOptions, + FormatOptions formatOptions, + ILogger logger, + CancellationToken cancellationToken); + + /// + /// Applies formatting and returns the changed for each . + /// + private ImmutableArray<(Document, Task<(SourceText originalText, SourceText? formattedText)>)> FormatFiles( + Solution solution, + ImmutableArray formattableDocuments, + FormatOptions formatOptions, + ILogger logger, + CancellationToken cancellationToken) + { + var formattedDocuments = ImmutableArray.CreateBuilder<(Document, Task<(SourceText originalText, SourceText? formattedText)>)>(formattableDocuments.Length); + + for (var index = 0; index < formattableDocuments.Length; index++) + { + var document = solution.GetDocument(formattableDocuments[index]); + if (document is null) + { + continue; + } + + var formatTask = Task.Run(async () => + { + var originalSourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + + var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + if (syntaxTree is null) + { + return (originalSourceText, null); + } + + var analyzerConfigOptions = document.Project.AnalyzerOptions.AnalyzerConfigOptionsProvider.GetOptions(syntaxTree); + var optionSet = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); + + return await GetFormattedSourceTextAsync(document, optionSet, analyzerConfigOptions, formatOptions, logger, cancellationToken).ConfigureAwait(false); + }, cancellationToken); + + formattedDocuments.Add((document, formatTask)); + } + + return formattedDocuments.ToImmutable(); + } + + /// + /// Get formatted for a . + /// + private async Task<(SourceText originalText, SourceText? formattedText)> GetFormattedSourceTextAsync( + Document document, + OptionSet optionSet, + AnalyzerConfigOptions analyzerConfigOptions, + FormatOptions formatOptions, + ILogger logger, + CancellationToken cancellationToken) + { + var originalSourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var formattedSourceText = await FormatFileAsync(document, originalSourceText, optionSet, analyzerConfigOptions, formatOptions, logger, cancellationToken).ConfigureAwait(false); + + return !formattedSourceText.ContentEquals(originalSourceText) || !formattedSourceText.Encoding?.Equals(originalSourceText.Encoding) == true + ? (originalSourceText, formattedSourceText) + : (originalSourceText, null); + } + + /// + /// Applies the changed to each formatted . + /// + private async Task ApplyFileChangesAsync( + Solution solution, + ImmutableArray<(Document, Task<(SourceText originalText, SourceText? formattedText)>)> formattedDocuments, + FormatOptions formatOptions, + ILogger logger, + List formattedFiles, + CancellationToken cancellationToken) + { + var formattedSolution = solution; + + for (var index = 0; index < formattedDocuments.Length; index++) + { + var (document, formatTask) = formattedDocuments[index]; + if (cancellationToken.IsCancellationRequested) + { + return formattedSolution; + } + + if (document?.FilePath is null) + { + continue; + } + + var (originalText, formattedText) = await formatTask.ConfigureAwait(false); + if (formattedText is null) + { + continue; + } + + var fileChanges = GetFileChanges(formatOptions, document, originalText, formattedText, formatOptions.ChangesAreErrors, logger); + formattedFiles.Add(new FormattedFile(document, fileChanges)); + + formattedSolution = formattedSolution.WithDocumentText(document.Id, formattedText, PreservationMode.PreserveIdentity); + } + + return formattedSolution; + } + + private ImmutableArray GetFileChanges(FormatOptions formatOptions, Document document, SourceText originalText, SourceText formattedText, bool changesAreErrors, ILogger logger) + { + var fileChanges = ImmutableArray.CreateBuilder(); + var changes = formattedText.GetTextChanges(originalText); + + for (var index = 0; index < changes.Count; index++) + { + var change = changes[index]; + + var changeMessage = changes.Count > 1 || change.NewText?.Length != formattedText.Length + ? BuildChangeMessage(change) + : string.Empty; + + var changePosition = originalText.Lines.GetLinePosition(change.Span.Start); + + var fileChange = new FileChange(changePosition, Name, $"{FormatWarningDescription}{changeMessage}"); + fileChanges.Add(fileChange); + + if (!formatOptions.SaveFormattedFiles || formatOptions.LogLevel == LogLevel.Debug) + { + logger.LogFormattingIssue(document, Name, fileChange, changesAreErrors); + } + } + + return fileChanges.ToImmutable(); + + static string BuildChangeMessage(TextChange change) + { + var isDelete = string.IsNullOrEmpty(change.NewText); + var isAdd = change.Span.Length == 0; + if (isDelete && isAdd) + { + return string.Empty; + } + + // Escape characters in the text changes so that it can be more easily read. + var textChange = change.NewText?.Replace(" ", "\\s").Replace("\t", "\\t").Replace("\n", "\\n").Replace("\r", "\\r"); + var message = isDelete + ? string.Format(Resources.Delete_0_characters, change.Span.Length) + : isAdd + ? string.Format(Resources.Insert_0, textChange) + : string.Format(Resources.Replace_0_characters_with_1, change.Span.Length, textChange); + return $" {message}"; + } + } + + protected static async Task IsSameDocumentAndVersionAsync(Document a, Document b, CancellationToken cancellationToken) + { + if (a == b) + { + return true; + } + + if (a.Id != b.Id) + { + return false; + } + + var aVersion = await a.GetTextVersionAsync(cancellationToken).ConfigureAwait(false); + var bVersion = await b.GetTextVersionAsync(cancellationToken).ConfigureAwait(false); + + return aVersion == bVersion; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Formatters/EndOfLineFormatter.cs b/src/BuiltInTools/dotnet-format/Formatters/EndOfLineFormatter.cs new file mode 100644 index 000000000000..59816f02f5d6 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Formatters/EndOfLineFormatter.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Formatters +{ + internal sealed class EndOfLineFormatter : DocumentFormatter + { + protected override string FormatWarningDescription => Resources.Fix_end_of_line_marker; + + public override string Name => "ENDOFLINE"; + public override FixCategory Category => FixCategory.Whitespace; + + internal override Task FormatFileAsync( + Document document, + SourceText sourceText, + OptionSet optionSet, + AnalyzerConfigOptions analyzerConfigOptions, + FormatOptions formatOptions, + ILogger logger, + CancellationToken cancellationToken) + { + return Task.Run(() => + { + if (!TryGetEndOfLine(analyzerConfigOptions, out var endOfLine)) + { + return sourceText; + } + + var newSourceText = sourceText; + for (var lineIndex = 0; lineIndex < newSourceText.Lines.Count; lineIndex++) + { + var line = newSourceText.Lines[lineIndex]; + var lineEndingSpan = new TextSpan(line.End, line.EndIncludingLineBreak - line.End); + + // Check for end of file + if (lineEndingSpan.IsEmpty) + { + break; + } + + var lineEnding = newSourceText.ToString(lineEndingSpan); + + if (lineEnding == endOfLine) + { + continue; + } + + var newLineChange = new TextChange(lineEndingSpan, endOfLine); + newSourceText = newSourceText.WithChanges(newLineChange); + } + + return newSourceText; + }); + } + + public static bool TryGetEndOfLine(AnalyzerConfigOptions analyzerConfigOptions, [NotNullWhen(true)] out string? endOfLine) + { + if (analyzerConfigOptions != null && + analyzerConfigOptions.TryGetValue("end_of_line", out var endOfLineOption)) + { + endOfLine = GetEndOfLine(endOfLineOption); + return true; + } + + endOfLine = null; + return false; + } + + private static string GetEndOfLine(string endOfLineOption) + { + return endOfLineOption switch + { + "lf" => "\n", + "cr" => "\r", + "crlf" => "\r\n", + _ => Environment.NewLine, + }; + } + + internal static string GetEndOfLineOption(string newLine) + { + return newLine switch + { + "\n" => "lf", + "\r" => "cr", + "\r\n" => "crlf", + _ => GetEndOfLineOption(Environment.NewLine), + }; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Formatters/FinalNewlineFormatter.cs b/src/BuiltInTools/dotnet-format/Formatters/FinalNewlineFormatter.cs new file mode 100644 index 000000000000..00a2cc1cfb37 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Formatters/FinalNewlineFormatter.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Formatters +{ + internal sealed class FinalNewlineFormatter : DocumentFormatter + { + protected override string FormatWarningDescription => Resources.Fix_final_newline; + + public override string Name => "FINALNEWLINE"; + public override FixCategory Category => FixCategory.Whitespace; + + internal override async Task FormatFileAsync( + Document document, + SourceText sourceText, + OptionSet optionSet, + AnalyzerConfigOptions analyzerConfigOptions, + FormatOptions formatOptions, + ILogger logger, + CancellationToken cancellationToken) + { + if (!analyzerConfigOptions.TryGetValue("insert_final_newline", out var insertFinalNewlineValue) || + !bool.TryParse(insertFinalNewlineValue, out var insertFinalNewline)) + { + return await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + } + + if (!EndOfLineFormatter.TryGetEndOfLine(analyzerConfigOptions, out var endOfLine)) + { + endOfLine = Environment.NewLine; + } + + var lastLine = sourceText.Lines[^1]; + var hasFinalNewline = lastLine.Span.IsEmpty; + + if (insertFinalNewline && !hasFinalNewline) + { + var finalNewlineSpan = new TextSpan(lastLine.End, 0); + var addNewlineChange = new TextChange(finalNewlineSpan, endOfLine); + sourceText = sourceText.WithChanges(addNewlineChange); + } + else if (!insertFinalNewline && hasFinalNewline) + { + // In the case of empty files where there is a single empty line, there is nothing to remove. + while (sourceText.Lines.Count > 1 && hasFinalNewline) + { + var lineBeforeLast = sourceText.Lines[^2]; + var finalNewlineSpan = new TextSpan(lineBeforeLast.End, lineBeforeLast.EndIncludingLineBreak - lineBeforeLast.End); + var removeNewlineChange = new TextChange(finalNewlineSpan, string.Empty); + sourceText = sourceText.WithChanges(removeNewlineChange); + + lastLine = sourceText.Lines[^1]; + hasFinalNewline = lastLine.Span.IsEmpty; + } + } + + return sourceText; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Formatters/ICodeFormatter.cs b/src/BuiltInTools/dotnet-format/Formatters/ICodeFormatter.cs new file mode 100644 index 000000000000..4ced6bacc721 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Formatters/ICodeFormatter.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Formatters +{ + internal interface ICodeFormatter + { + /// + /// Gets the fix category this formatter belongs to. + /// + FixCategory Category { get; } + + /// + /// Applies formatting and returns a formatted . + /// + Task FormatAsync( + Workspace workspace, + Solution solution, + ImmutableArray formattableDocuments, + FormatOptions options, + ILogger logger, + List formattedFiles, + CancellationToken cancellationToken); + } +} diff --git a/src/BuiltInTools/dotnet-format/Formatters/OrganizeImportsFormatter.cs b/src/BuiltInTools/dotnet-format/Formatters/OrganizeImportsFormatter.cs new file mode 100644 index 000000000000..a35008ddffe1 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Formatters/OrganizeImportsFormatter.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Formatters +{ + /// + /// OrganizeImportsFormatter that uses the to format document import directives. + /// + internal sealed class OrganizeImportsFormatter : DocumentFormatter + { + protected override string FormatWarningDescription => Resources.Fix_imports_ordering; + private readonly DocumentFormatter _endOfLineFormatter = new EndOfLineFormatter(); + + public override string Name => "IMPORTS"; + public override FixCategory Category => FixCategory.CodeStyle; + + internal override async Task FormatFileAsync( + Document document, + SourceText sourceText, + OptionSet optionSet, + AnalyzerConfigOptions analyzerConfigOptions, + FormatOptions formatOptions, + ILogger logger, + CancellationToken cancellationToken) + { + try + { + // Only run formatter if the user has specifically configured one of the driving properties. + if (!analyzerConfigOptions.TryGetValue("dotnet_sort_system_directives_first", out _) && + !analyzerConfigOptions.TryGetValue("dotnet_separate_import_directive_groups", out _)) + { + return sourceText; + } + + var organizedDocument = await Formatter.OrganizeImportsAsync(document, cancellationToken); + + var isSameVersion = await IsSameDocumentAndVersionAsync(document, organizedDocument, cancellationToken).ConfigureAwait(false); + if (isSameVersion) + { + return sourceText; + } + + // Because the Formatter does not abide the `end_of_line` option we have to fix up the ends of the organized lines. + // See https://github.com/dotnet/roslyn/issues/44136 + var organizedSourceText = await organizedDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); + return await _endOfLineFormatter.FormatFileAsync(organizedDocument, organizedSourceText, optionSet, analyzerConfigOptions, formatOptions, logger, cancellationToken).ConfigureAwait(false); + } + catch (InsufficientExecutionStackException) + { + // This case is normally not hit when running against a handwritten code file. + // https://github.com/dotnet/roslyn/issues/44710#issuecomment-636253053 + logger.LogWarning(Resources.Unable_to_organize_imports_for_0_The_document_is_too_complex, Path.GetFileName(document.FilePath)); + return sourceText; + } + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Formatters/WhitespaceFormatter.cs b/src/BuiltInTools/dotnet-format/Formatters/WhitespaceFormatter.cs new file mode 100644 index 000000000000..b537d2e8791e --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Formatters/WhitespaceFormatter.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Formatters +{ + /// + /// CodeFormatter that uses the to format document whitespace. + /// + internal sealed class WhitespaceFormatter : DocumentFormatter + { + protected override string FormatWarningDescription => Resources.Fix_whitespace_formatting; + + public override string Name => "WHITESPACE"; + public override FixCategory Category => FixCategory.Whitespace; + + internal override async Task FormatFileAsync( + Document document, + SourceText sourceText, + OptionSet optionSet, + AnalyzerConfigOptions analyzerConfigOptions, + FormatOptions formatOptions, + ILogger logger, + CancellationToken cancellationToken) + { + if (formatOptions.SaveFormattedFiles) + { + return await GetFormattedDocument(document, optionSet, cancellationToken).ConfigureAwait(false); + } + else + { + return await GetFormattedDocumentWithDetailedChanges(document, sourceText, optionSet, cancellationToken).ConfigureAwait(false); + } + } + + /// + /// Returns a formatted with a single that encompasses the entire document. + /// + private static async Task GetFormattedDocument(Document document, OptionSet optionSet, CancellationToken cancellationToken) + { + var formattedDocument = await Formatter.FormatAsync(document, optionSet, cancellationToken).ConfigureAwait(false); + return await formattedDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); + } + + /// + /// Returns a formatted with multiple s for each formatting change. + /// + private static async Task GetFormattedDocumentWithDetailedChanges(Document document, SourceText sourceText, OptionSet optionSet, CancellationToken cancellationToken) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + // Since we've already checked that formatable documents support syntax tree, we know the `root` is not null. + var formattingTextChanges = Formatter.GetFormattedTextChanges(root!, document.Project.Solution.Workspace, optionSet, cancellationToken); + + return sourceText.WithChanges(formattingTextChanges); + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Logging/IIssueFormatter.cs b/src/BuiltInTools/dotnet-format/Logging/IIssueFormatter.cs new file mode 100644 index 000000000000..b2da7937cc3e --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Logging/IIssueFormatter.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Tools.Logging +{ + public interface IIssueFormatter + { + string FormatIssue(Document document, string severity, string issueId, int lineNumber, int charNumber, string message); + } +} diff --git a/src/BuiltInTools/dotnet-format/Logging/ILoggerExtensions.cs b/src/BuiltInTools/dotnet-format/Logging/ILoggerExtensions.cs new file mode 100644 index 000000000000..0adaa9503013 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Logging/ILoggerExtensions.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Tools.Logging; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools +{ + internal static class ILoggerExtensions + { + private static readonly string s_errorSeverityString = DiagnosticSeverity.Error.ToString().ToLower(); + + public static IIssueFormatter IssueFormatter { get; set; } = new MSBuildIssueFormatter(); + + public static string LogFormattingIssue(this ILogger logger, Document document, string formatterName, FileChange fileChange, bool changesAreErrors) + => LogIssue(logger, document, s_errorSeverityString, formatterName, fileChange.LineNumber, fileChange.CharNumber, fileChange.FormatDescription, changesAreErrors); + + public static string LogDiagnosticIssue(this ILogger logger, Document document, LinePosition diagnosticPosition, Diagnostic diagnostic, bool changesAreErrors) + => LogIssue(logger, document, diagnostic.Severity.ToString().ToLower(), diagnostic.Id, diagnosticPosition.Line + 1, diagnosticPosition.Character + 1, diagnostic.GetMessage(), changesAreErrors); + + private static string LogIssue(ILogger logger, Document document, string severity, string issueId, int lineNumber, int charNumber, string message, bool changesAreErrors) + { + var formattedMessage = IssueFormatter.FormatIssue(document, severity, issueId, lineNumber, charNumber, message); + + if (changesAreErrors) + { + logger.LogError(formattedMessage); + } + else + { + logger.LogWarning(formattedMessage); + } + + return formattedMessage; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Logging/MSBuildIssueFormatter.cs b/src/BuiltInTools/dotnet-format/Logging/MSBuildIssueFormatter.cs new file mode 100644 index 000000000000..bbcb8f539890 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Logging/MSBuildIssueFormatter.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Tools.Logging +{ + internal sealed class MSBuildIssueFormatter : IIssueFormatter + { + public string FormatIssue(Document document, string severity, string issueId, int lineNumber, int charNumber, string message) + => $"{document.FilePath ?? document.Name}({lineNumber},{charNumber}): {severity} {issueId}: {message} [{document.Project.FilePath}]"; + } +} diff --git a/src/BuiltInTools/dotnet-format/Logging/NullScope.cs b/src/BuiltInTools/dotnet-format/Logging/NullScope.cs new file mode 100644 index 000000000000..596fd890fb63 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Logging/NullScope.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Tools.Logging +{ + internal class NullScope : IDisposable + { + public static NullScope Instance { get; } = new NullScope(); + + private NullScope() + { + } + + public void Dispose() + { + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Logging/SimpleConsoleLogger.cs b/src/BuiltInTools/dotnet-format/Logging/SimpleConsoleLogger.cs new file mode 100644 index 000000000000..6c25bfbb85c6 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Logging/SimpleConsoleLogger.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.CommandLine; +using System.CommandLine.Rendering; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Logging +{ + internal class SimpleConsoleLogger : ILogger + { + private readonly object _gate = new object(); + + private readonly IConsole _console; + private readonly ITerminal _terminal; + private readonly LogLevel _minimalLogLevel; + private readonly LogLevel _minimalErrorLevel; + + private static ImmutableDictionary LogLevelColorMap => new Dictionary + { + [LogLevel.Critical] = ConsoleColor.Red, + [LogLevel.Error] = ConsoleColor.Red, + [LogLevel.Warning] = ConsoleColor.Yellow, + [LogLevel.Information] = ConsoleColor.White, + [LogLevel.Debug] = ConsoleColor.Gray, + [LogLevel.Trace] = ConsoleColor.Gray, + [LogLevel.None] = ConsoleColor.White, + }.ToImmutableDictionary(); + + public SimpleConsoleLogger(IConsole console, LogLevel minimalLogLevel, LogLevel minimalErrorLevel) + { + _terminal = console.GetTerminal(); + _console = console; + _minimalLogLevel = minimalLogLevel; + _minimalErrorLevel = minimalErrorLevel; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + if (!IsEnabled(logLevel)) + { + return; + } + + lock (_gate) + { + var message = formatter(state, exception); + var logToErrorStream = logLevel >= _minimalErrorLevel; + if (_terminal is null) + { + LogToConsole(_console, message, logToErrorStream); + } + else + { + LogToTerminal(message, logLevel, logToErrorStream); + } + } + } + + public bool IsEnabled(LogLevel logLevel) + { + return (int)logLevel >= (int)_minimalLogLevel; + } + + public IDisposable? BeginScope(TState state) where TState : notnull + { + return NullScope.Instance; + } + + private void LogToTerminal(string message, LogLevel logLevel, bool logToErrorStream) + { + var messageColor = LogLevelColorMap[logLevel]; + _terminal.ForegroundColor = messageColor; + + LogToConsole(_terminal, message, logToErrorStream); + + _terminal.ResetColor(); + } + + private static void LogToConsole(IConsole console, string message, bool logToErrorStream) + { + if (logToErrorStream) + { + console.Error.Write($"{message}{Environment.NewLine}"); + } + else + { + console.Out.Write($" {message}{Environment.NewLine}"); + } + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Logging/SimpleConsoleLoggerFactoryExtensions.cs b/src/BuiltInTools/dotnet-format/Logging/SimpleConsoleLoggerFactoryExtensions.cs new file mode 100644 index 000000000000..5795b9e3bd7d --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Logging/SimpleConsoleLoggerFactoryExtensions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.CommandLine; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Logging +{ + internal static class SimpleConsoleLoggerFactoryExtensions + { + public static ILoggerFactory AddSimpleConsole(this ILoggerFactory factory, IConsole console, LogLevel minimalLogLevel, LogLevel minimalErrorLevel) + { + factory.AddProvider(new SimpleConsoleLoggerProvider(console, minimalLogLevel, minimalErrorLevel)); + return factory; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Logging/SimpleConsoleLoggerProvider.cs b/src/BuiltInTools/dotnet-format/Logging/SimpleConsoleLoggerProvider.cs new file mode 100644 index 000000000000..1b98c212df40 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Logging/SimpleConsoleLoggerProvider.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.CommandLine; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Logging +{ + internal class SimpleConsoleLoggerProvider : ILoggerProvider + { + private readonly IConsole _console; + private readonly LogLevel _minimalLogLevel; + private readonly LogLevel _minimalErrorLevel; + + public SimpleConsoleLoggerProvider(IConsole console, LogLevel minimalLogLevel, LogLevel minimalErrorLevel) + { + _console = console; + _minimalLogLevel = minimalLogLevel; + _minimalErrorLevel = minimalErrorLevel; + } + + public ILogger CreateLogger(string name) + { + return new SimpleConsoleLogger(_console, _minimalLogLevel, _minimalErrorLevel); + } + + public void Dispose() + { + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Program.cs b/src/BuiltInTools/dotnet-format/Program.cs new file mode 100644 index 000000000000..a6109488c6e4 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Program.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Tools.Commands; + +namespace Microsoft.CodeAnalysis.Tools +{ + internal class Program + { + private static async Task Main(string[] args) + { + var rootCommand = RootFormatCommand.GetCommand(); + return await rootCommand.Parse(args).InvokeAsync(CancellationToken.None); + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Properties/launchSettings.json b/src/BuiltInTools/dotnet-format/Properties/launchSettings.json new file mode 100644 index 000000000000..65c353dbbf89 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "profiles": { + "dotnet-format (--dry-run)": { + "commandName": "Project", + "commandLineArgs": "format.sln -v diag --verify-no-changes", + "workingDirectory": "$(SolutionDir)" + }, + "dotnet-format": { + "commandName": "Project", + "commandLineArgs": "format.sln -v diag", + "workingDirectory": "$(SolutionDir)" + } + } +} \ No newline at end of file diff --git a/src/BuiltInTools/dotnet-format/README.md b/src/BuiltInTools/dotnet-format/README.md new file mode 100644 index 000000000000..9d31ed0dbc52 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/README.md @@ -0,0 +1,118 @@ + +## dotnet-format +dotnet-format + +|Branch| Windows (Debug)| Windows (Release)| Linux (Debug) | Linux (Release) | Localization (Debug) | Localization (Release) | +|---|:--:|:--:|:--:|:--:|:--:|:--:| +[main](https://github.com/dotnet/format/tree/main)|[![Build Status](https://dev.azure.com/dnceng-public/public/_apis/build/status%2Fdotnet%2Fformat%2Fdotnet.format?branchName=main&jobName=Windows&configuration=Windows%20Debug)](https://dev.azure.com/dnceng-public/public/_build/latest?definitionId=176&branchName=main)|[![Build Status](https://dev.azure.com/dnceng-public/public/_apis/build/status%2Fdotnet%2Fformat%2Fdotnet.format?branchName=main&jobName=Windows&configuration=Windows%20Release)](https://dev.azure.com/dnceng-public/public/_build/latest?definitionId=176&branchName=main)|[![Build Status](https://dev.azure.com/dnceng-public/public/_apis/build/status%2Fdotnet%2Fformat%2Fdotnet.format?branchName=main&jobName=Linux&configuration=Linux%20Debug)](https://dev.azure.com/dnceng-public/public/_build/latest?definitionId=176&branchName=main)|[![Build Status](https://dev.azure.com/dnceng-public/public/_apis/build/status%2Fdotnet%2Fformat%2Fdotnet.format?branchName=main&jobName=Linux&configuration=Linux%20Release)](https://dev.azure.com/dnceng-public/public/_build/latest?definitionId=176&branchName=main)|[![Build Status](https://dev.azure.com/dnceng-public/public/_apis/build/status%2Fdotnet%2Fformat%2Fdotnet.format?branchName=main&jobName=Linux_Spanish&configuration=Linux_Spanish%20Debug)](https://dev.azure.com/dnceng-public/public/_build/latest?definitionId=176&branchName=main)|[![Build Status](https://dev.azure.com/dnceng-public/public/_apis/build/status%2Fdotnet%2Fformat%2Fdotnet.format?branchName=main&jobName=Linux_Spanish&configuration=Linux_Spanish%20Release)](https://dev.azure.com/dnceng-public/public/_build/latest?definitionId=176&branchName=main)| + +`dotnet-format` is a code formatter for `dotnet` that applies style preferences to a project or solution. Preferences will be read from an `.editorconfig` file, if present, otherwise a default set of preferences will be used. At this time `dotnet-format` is able to format C# and Visual Basic projects with a subset of [supported .editorconfig options](./docs/Supported-.editorconfig-options.md). + +### How To Use + +dotnet-format is now part of the .NET 6 SDK. Invoking the `dotnet format` command will fix whitespace, code style, and analyzer issues by default. dotnet-format will look in the current directory for a project or solution file and use that as the workspace to format. If more than one project or solution file is present in the current directory, you will need to specify the workspace to format. You can control how verbose the output will be by using the `--verbosity` option. + +There are also 3 subcommands to target specific scenarios: +- `dotnet format whitespace`: fixes whitespace +- `dotnet format style`: runs code style analyzers +- `dotnet format analyzers`: runs third party analyzers + +#### Common options for all commands +- `--no-restore`: Doesn't execute an implicit restore before formatting. +- `--include-generated`: Format files generated by the SDK +- `--include`: A space-separated list of relative file or folder paths to include in formatting. All files are formatted if empty. (e.g. `--include path/to/file.cs path/to/folder folder2/*.vb`) +- `--exclude`: A space-separated list of relative file or folder paths to exclude from formatting. (e.g. `--exclude path/to/file.cs path/to/folder folder2/*.vb`) +- `--verbosity`: Set the verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] +- `--report`: Accepts a file path, which if provided, will produce a json report in the given directory. +- `--binarylog`: Log all project or solution load information to a binary log file. +- `--verify-no-changes`: Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + +#### Unique options for `dotnet format` + - `--diagnostics`: A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + - `--severity`: The severity of diagnostics to fix. Allowed values are info, warn, and error. + +Note: if the user specifies a severity here it is used for both style and analyzers. + +#### Unique options for `dotnet format whitespace` + - `--folder`: Whether to treat the workspace argument as a simple folder of files. + +#### Unique options for `dotnet format style` + - `--diagnostics`: A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + - `--severity`: The severity of diagnostics to fix. Allowed values are info, warn, and error. + +#### Unique options for `dotnet format analyzers` + - `--diagnostics`: A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + - `--severity`: The severity of diagnostics to fix. Allowed values are info, warn, and error. + +### Examples + +Add `format` after `dotnet` and before the command arguments that you want to run: + +| Examples | Description | +| ---------------------------------------------------------------- |--------------------------------------------------------------------------------------------------- | +| `dotnet format` | Formats and runs analysis for the project or solution in the current directory. | +| `dotnet format -v diag` | Formats and runs analysis with very verbose logging. | +| `dotnet format ` | Formats and runs analysis for a specific project or solution. | +| `dotnet format --severity error` | Formats, fixes codestyle errors, and fixes 3rd party analyzer errors. | +| `dotnet format whitespace --folder` | Formats a particular folder and subfolders. | +| `dotnet format style ` | Fixes only codestyle analyzer warnings. | +| `dotnet format style --severity error --no-restore` | Fixes only codestyle analyzer errors without performing an implicit restore. | +| `dotnet format style --diagnostics IDE0005` | Fixes only codestyle analyzer warnings for the IDE0005 diagnostic. | +| `dotnet format analyzers --severity error` | Fixes only 3rd party analyzer errors. | +| `dotnet format --include Program.cs Utility\Logging.cs` | Formats the files Program.cs and Utility\Logging.cs | +| `dotnet format --verify-no-changes` | Formats but does not save. Returns a non-zero exit code if any files would have been changed. | +| `dotnet format --report ` | Formats and saves a json report file to the given directory. | +| `dotnet format --include test/Utilities/*.cs --folder` | Formats the files expanded from native shell globbing (e.g. bash). Space-separated list of files are fed to formatter in this case. Also applies to `--exclude` option. | +| `dotnet format --include 'test/Utilities/*.cs' --folder` | With single quotes, formats the files expanded from built-in glob expansion. A single file pattern is fed to formatter, which gets expanded internally. Also applies to `--exclude` option. | +| `ls tests/Utilities/*.cs \| dotnet format --include - --folder` | Formats the list of files redirected from pipeline via standard input. Formatter will iterate over `Console.In` to read the list of files. Also applies to `--exclude` option. | + +### How to install Development Builds + +Development builds of `dotnet-format` are being hosted on Azure Packages. + +Note: After installing .NET 6 Preview 7 SDK or higher you will need to run the dotnet-format global tool by invoking `dotnet-format`. Invoking `dotnet format` will invoke the SDK's copy of dotnet-format. + +#### Installing the latest build + +You can install the latest build of the tool using the following command. + +```console +dotnet tool install -g dotnet-format --version "8.*" --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/index.json +``` + +#### Installing a specific build + +You can install a specific build of the tool using the following command. Be sure to update the version string to match the intended version. + +```console +dotnet tool install -g dotnet-format --version "8.0.446201" --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/index.json +``` + +You can visit the [dotnet8 feed](https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet8/NuGet/dotnet-format/versions) for a full list of versions. + +#### Looking for earlier versions of dotnet-format? + +Visit the [dotnet7 feed](https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet7/NuGet/dotnet-format/versions) or [dotnet6 feed](https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet6/NuGet/dotnet-format/versions) instead. Be sure to use the source URL appropriate for the version that you are installing. + +### How To Build From Source + +You can build and package the tool using the following commands. The instructions assume that you are in the root of the repository. + +```console +build -pack +# The final line from the build will read something like +# Successfully created package '..\artifacts\packages\Debug\Shipping\dotnet-format.8.0.0-dev.nupkg'. +# Use the value that is in the form `8.0.0-dev` as the version in the next command. +dotnet tool install --add-source .\artifacts\packages\Debug\Shipping -g dotnet-format --version +dotnet format +``` + +> Note: On macOS and Linux, `.\artifacts` will need be switched to `./artifacts` to accommodate for the different slash directions. + +### How To Uninstall + +You can uninstall the tool using the following command. + +```console +dotnet tool uninstall -g dotnet-format +``` \ No newline at end of file diff --git a/src/BuiltInTools/dotnet-format/Reflection/RemoveUnnecessaryImportsHelper.cs b/src/BuiltInTools/dotnet-format/Reflection/RemoveUnnecessaryImportsHelper.cs new file mode 100644 index 000000000000..2fa9a79e8cc8 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Reflection/RemoveUnnecessaryImportsHelper.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Reflection; + +namespace Microsoft.CodeAnalysis.Tools.Reflection +{ + internal static class RemoveUnnecessaryImportsHelper + { + private static readonly Assembly? s_microsoftCodeAnalysisFeaturesAssembly = Assembly.Load(new AssemblyName("Microsoft.CodeAnalysis.Features")); + private static readonly Type? s_abstractRemoveUnnecessaryImportsCodeFixProviderType = s_microsoftCodeAnalysisFeaturesAssembly?.GetType("Microsoft.CodeAnalysis.RemoveUnnecessaryImports.AbstractRemoveUnnecessaryImportsCodeFixProvider"); + private static readonly MethodInfo? s_removeUnnecessaryImportsAsyncMethod = s_abstractRemoveUnnecessaryImportsCodeFixProviderType?.GetMethod("RemoveUnnecessaryImportsAsync", BindingFlags.Static | BindingFlags.NonPublic); + + public static async Task RemoveUnnecessaryImportsAsync(Document document, CancellationToken cancellationToken) + { + if (s_removeUnnecessaryImportsAsyncMethod is null) + { + return document; + } + + return await (Task)s_removeUnnecessaryImportsAsyncMethod.Invoke(obj: null, new object[] { document, cancellationToken })!; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/ReportWriter.cs b/src/BuiltInTools/dotnet-format/ReportWriter.cs new file mode 100644 index 000000000000..14bc3ea590b0 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/ReportWriter.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Text.Json; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools +{ + internal static class ReportWriter + { + public static void Write(string reportPath, IEnumerable formattedFiles, ILogger logger) + { + var reportFilePath = GetReportFilePath(reportPath); + var reportFolderPath = Path.GetDirectoryName(reportFilePath); + + if (!string.IsNullOrEmpty(reportFolderPath) && !Directory.Exists(reportFolderPath)) + { + Directory.CreateDirectory(reportFolderPath); + } + + logger.LogInformation(Resources.Writing_formatting_report_to_0, reportFilePath); + + var seralizerOptions = new JsonSerializerOptions + { + WriteIndented = true + }; + var formattedFilesJson = JsonSerializer.Serialize(formattedFiles, seralizerOptions); + + File.WriteAllText(reportFilePath, formattedFilesJson); + } + + private static string GetReportFilePath(string reportPath) + { + var defaultReportName = "format-report.json"; + if (reportPath.EndsWith(".json")) + { + return reportPath; + } + else if (reportPath == ".") + { + return Path.Combine(Environment.CurrentDirectory, defaultReportName); + } + else + { + return Path.Combine(reportPath, defaultReportName); + } + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Resources.resx b/src/BuiltInTools/dotnet-format/Resources.resx new file mode 100644 index 000000000000..999aa66a5a2d --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Resources.resx @@ -0,0 +1,366 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The project file '{0}' does not exist. + + + The solution file '{0}' does not exist. + + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the <workspace> argument. + + + Could not find a MSBuild project file or solution file in '{0}'. Specify which to use with the <workspace> argument. + + + Failed to save formatting changes. + + + The file '{0}' does not appear to be a valid project or solution file. + + + Multiple MSBuild project files found in '{0}'. Specify which to use with the <workspace> argument. + + + Multiple MSBuild solution files found in '{0}'. Specify which to use with the <workspace> argument. + + + Format complete in {0}ms. + + + Formatting code file '{0}'. + + + Formatting code files in workspace '{0}'. + + + Could not format '{0}'. Format currently supports only C# and Visual Basic projects. + + + Formatted {0} of {1} files. + + + Loading workspace. + + + Skipping referenced project '{0}'. + + + Set the verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] + + + A path to a solution file, a project file, or a folder containing a solution or project file. If a path is not specified then the current directory is used. + + + Formats files without saving changes to disk. Terminates with a non-zero exit code if any files were formatted. + + + A list of relative file or folder paths to include in formatting. All files are formatted if empty. + + + Formatted code file '{0}'. + + + Determining formattable files. + + + Complete in {0}ms. + + + Running formatters. + + + Warnings were encountered while loading the workspace. Set the verbosity option to the 'diagnostic' level to log warnings. + + + Fix final newline. + + + Fix end of line marker. + + + Fix whitespace formatting. + + + Fix file encoding. + + + Whether to treat the `<workspace>` argument as a simple folder of files. + + + Accepts a file path, which if provided, will produce a json report in the given directory. + + + Writing formatting report to: '{0}'. + + + A list of relative file or folder paths to exclude from formatting. + + + Using MSBuild.exe located in '{0}'. + + + Unable to locate MSBuild. Ensure the .NET SDK was installed with the official installer. + + + Include generated code files in formatting operations. + + + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + + + Unable to locate dotnet CLI. Ensure that it is on the PATH. + + + The dotnet CLI version is '{0}'. + + + Fix imports ordering. + + + Unable to organize imports for '{0}'. The document is too complex. + + + Run code style analyzers and apply fixes. + + + Run 3rd party analyzers and apply fixes. + + + Analyzer Reference + + + Code Style + + + Analysis complete in {0}ms. + + + Determining diagnostics... + + + Failed to apply code fix {0} for {1}: {2} + + + Fixing diagnostics... + + + Running {0} analysis. + + + Solution {0} has no projects + + + Unable to fix {0}. Code fix {1} doesn't support Fix All in Solution. + + + Unable to fix {0}. No associated code fix found. + + + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + + + Running {0} analyzers on {1}. + + + The dotnet runtime version is '{0}'. + + + Remove unnecessary import. + + + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + + + Unable to fix {0}. Code fix {1} returned an unexpected operation. + + + Run whitespace formatting. Run by default when not applying fixes. + + + The dotnet format version is '{0}'. + + + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + + + The '--diagnostics' option only applies when fixing style or running analyzers. + + + Cannot specify the '--folder' option when running analyzers. + + + Cannot specify the '--folder' option when fixing style. + + + Project {0} is using configuration from '{1}'. + + + Doesn't execute an implicit restore before formatting. + + + Cannot specify the '--folder' option with '--no-restore'. + + + Delete {0} characters. + + + Insert '{0}'. + + + Replace {0} characters with '{1}'. + + + Log all project or solution load information to a binary log file. + + + Cannot specify the '--folder' option when writing a binary log. + + + PROJECT | SOLUTION + + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. + + + Accepts a file path which if provided will produce a json report in the given directory. + + + Format files generated by the SDK. + + + The severity of diagnostics to fix. Allowed values are info, warn, and error. + + + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + + + Formats code to match editorconfig settings. + + + Run 3rd party analyzers and apply fixes. + + + Run whitespace formatting. + + + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + + \ No newline at end of file diff --git a/src/BuiltInTools/dotnet-format/Resources/icon.png b/src/BuiltInTools/dotnet-format/Resources/icon.png new file mode 100644 index 000000000000..992a8f583faf Binary files /dev/null and b/src/BuiltInTools/dotnet-format/Resources/icon.png differ diff --git a/src/BuiltInTools/dotnet-format/Utilities/DotNetHelper.cs b/src/BuiltInTools/dotnet-format/Utilities/DotNetHelper.cs new file mode 100644 index 000000000000..9fb5e89abb0f --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Utilities/DotNetHelper.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Utilities +{ + internal static class DotNetHelper + { + public static async Task PerformRestoreAsync(string workspaceFilePath, ILogger logger) + { + var processInfo = ProcessRunner.CreateProcess("dotnet", $"restore \"{workspaceFilePath}\"", captureOutput: true, displayWindow: false); + var restoreResult = await processInfo.Result; + + logger.LogDebug(string.Join(Environment.NewLine, restoreResult.OutputLines)); + + return restoreResult.ExitCode; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Utilities/EditorConfigFinder.cs b/src/BuiltInTools/dotnet-format/Utilities/EditorConfigFinder.cs new file mode 100644 index 000000000000..48383ab4ce72 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Utilities/EditorConfigFinder.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.Tools.Utilities +{ + internal static class EditorConfigFinder + { + public static ImmutableArray GetEditorConfigPaths(string path) + { + // If the path is to a file then remove the file name and process the + // folder path. + var startPath = Directory.Exists(path) + ? path + : Path.GetDirectoryName(path); + + if (!Directory.Exists(startPath)) + { + return ImmutableArray.Empty; + } + + var editorConfigPaths = ImmutableArray.CreateBuilder(16); + + var directory = new DirectoryInfo(path); + + // Find .editorconfig files contained unders the folder path. + var files = directory.GetFiles(".editorconfig", SearchOption.AllDirectories); + for (var index = 0; index < files.Length; index++) + { + editorConfigPaths.Add(files[index].FullName); + } + + // Walk from the folder path up to the drive root addings .editorconfig files. + while (directory.Parent != null) + { + directory = directory.Parent; + + files = directory.GetFiles(".editorconfig", SearchOption.TopDirectoryOnly); + if (files.Length == 1) + { + editorConfigPaths.Add(files[0].FullName); + } + } + + return editorConfigPaths.ToImmutable(); + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Utilities/EditorConfigOptions.cs b/src/BuiltInTools/dotnet-format/Utilities/EditorConfigOptions.cs new file mode 100644 index 000000000000..50f5545bdfc1 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Utilities/EditorConfigOptions.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.Tools.Utilities +{ + internal sealed class EditorConfigOptions : AnalyzerConfigOptions + { + private readonly IReadOnlyDictionary _backing; + + public EditorConfigOptions(IReadOnlyDictionary backing) + { + _backing = backing; + } + + public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value) + => _backing.TryGetValue(key, out value); + } +} diff --git a/src/BuiltInTools/dotnet-format/Utilities/GeneratedCodeUtilities.cs b/src/BuiltInTools/dotnet-format/Utilities/GeneratedCodeUtilities.cs new file mode 100644 index 000000000000..90aacae9894d --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Utilities/GeneratedCodeUtilities.cs @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.Tools.Utilities +{ + internal static class GeneratedCodeUtilities + { + private static readonly string[] s_autoGeneratedStrings = new[] { " s_isCSharpCommentTrivia = + (syntaxTrivia) => syntaxTrivia.IsKind(CSharp.SyntaxKind.SingleLineCommentTrivia) + || syntaxTrivia.IsKind(CSharp.SyntaxKind.MultiLineCommentTrivia) + || syntaxTrivia.IsKind(CSharp.SyntaxKind.SingleLineDocumentationCommentTrivia) + || syntaxTrivia.IsKind(CSharp.SyntaxKind.MultiLineDocumentationCommentTrivia); + + private static readonly Func s_isVisualBasicCommentTrivia = + (syntaxTrivia) => syntaxTrivia.IsKind(VisualBasic.SyntaxKind.CommentTrivia) + || syntaxTrivia.IsKind(VisualBasic.SyntaxKind.DocumentationCommentTrivia); + + internal static async Task IsGeneratedCodeAsync(SyntaxTree syntaxTree, CancellationToken cancellationToken) + { + if (IsGeneratedCodeFileName(syntaxTree.FilePath)) + { + return true; + } + + var isCommentTrivia = syntaxTree.Options.Language == LanguageNames.CSharp + ? s_isCSharpCommentTrivia + : s_isVisualBasicCommentTrivia; + + var syntaxRoot = await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); + return BeginsWithAutoGeneratedComment(syntaxRoot, isCommentTrivia); + } + + private static bool IsGeneratedCodeFileName(string? filePath) + { + if (!string.IsNullOrEmpty(filePath)) + { + var fileName = Path.GetFileName(filePath); + if (fileName.StartsWith("TemporaryGeneratedFile_", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + var extension = Path.GetExtension(fileName); + if (!string.IsNullOrEmpty(extension)) + { + var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath); + if (fileNameWithoutExtension.EndsWith(".designer", StringComparison.OrdinalIgnoreCase) || + fileNameWithoutExtension.EndsWith(".generated", StringComparison.OrdinalIgnoreCase) || + fileNameWithoutExtension.EndsWith(".g", StringComparison.OrdinalIgnoreCase) || + fileNameWithoutExtension.EndsWith(".g.i", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + } + + return false; + } + + private static bool BeginsWithAutoGeneratedComment(SyntaxNode syntaxRoot, Func isComment) + { + if (syntaxRoot.HasLeadingTrivia) + { + var leadingTrivia = syntaxRoot.GetLeadingTrivia(); + + foreach (var trivia in leadingTrivia) + { + if (!isComment(trivia)) + { + continue; + } + + var text = trivia.ToString(); + + // Check to see if the text of the comment contains an auto generated comment. + foreach (var autoGenerated in s_autoGeneratedStrings) + { + if (text.Contains(autoGenerated)) + { + return true; + } + } + } + } + + return false; + } + + internal static bool? GetIsGeneratedCodeFromOptions(AnalyzerConfigOptions options) + { + // Check for explicit user configuration for generated code. + // generated_code = true | false + if (options.TryGetValue("generated_code", out var optionValue) && + bool.TryParse(optionValue, out var boolValue)) + { + return boolValue; + } + + // Either no explicit user configuration or we don't recognize the option value. + return null; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Utilities/ProcessRunner.cs b/src/BuiltInTools/dotnet-format/Utilities/ProcessRunner.cs new file mode 100644 index 000000000000..7ab181105c84 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Utilities/ProcessRunner.cs @@ -0,0 +1,204 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.ObjectModel; +using System.Diagnostics; + +namespace Microsoft.CodeAnalysis.Tools.Utilities +{ + public readonly struct ProcessResult + { + public Process Process { get; } + public int ExitCode { get; } + public ReadOnlyCollection OutputLines { get; } + public ReadOnlyCollection ErrorLines { get; } + + public ProcessResult(Process process, int exitCode, ReadOnlyCollection outputLines, ReadOnlyCollection errorLines) + { + Process = process; + ExitCode = exitCode; + OutputLines = outputLines; + ErrorLines = errorLines; + } + } + + public readonly struct ProcessInfo + { + public Process Process { get; } + public ProcessStartInfo StartInfo { get; } + public Task Result { get; } + + public int Id => Process.Id; + + public ProcessInfo(Process process, ProcessStartInfo startInfo, Task result) + { + Process = process; + StartInfo = startInfo; + Result = result; + } + } + + public static class ProcessRunner + { + public static void OpenFile(string file) + { + if (File.Exists(file)) + { + Process.Start(file); + } + } + + public static ProcessInfo CreateProcess( + string executable, + string arguments, + bool lowPriority = false, + string? workingDirectory = null, + bool captureOutput = false, + bool displayWindow = true, + Dictionary? environmentVariables = null, + Action? onProcessStartHandler = null, + CancellationToken cancellationToken = default) => + CreateProcess( + CreateProcessStartInfo(executable, arguments, workingDirectory, captureOutput, displayWindow, environmentVariables), + lowPriority: lowPriority, + onProcessStartHandler: onProcessStartHandler, + cancellationToken: cancellationToken); + + public static ProcessInfo CreateProcess( + ProcessStartInfo processStartInfo, + bool lowPriority = false, + Action? onProcessStartHandler = null, + CancellationToken cancellationToken = default) + { + var redirectInitiated = new ManualResetEventSlim(); + + var errorLines = new List(); + var outputLines = new List(); + var process = new Process(); + var tcs = new TaskCompletionSource(); + + process.EnableRaisingEvents = true; + process.StartInfo = processStartInfo; + + process.OutputDataReceived += (s, e) => + { + if (e.Data != null) + { + outputLines.Add(e.Data); + } + }; + + process.ErrorDataReceived += (s, e) => + { + if (e.Data != null) + { + errorLines.Add(e.Data); + } + }; + + process.Exited += (s, e) => + { + // We must call WaitForExit to make sure we've received all OutputDataReceived/ErrorDataReceived calls + // or else we'll be returning a list we're still modifying. For paranoia, we'll start a task here rather + // than enter right back into the Process type and start a wait which isn't guaranteed to be safe. + Task.Run(() => + { + // WaitForExit will only wait for the process to finish redirecting its output/error if we call + // BeginOutputReadLine/BeginErrorReadLine prior to calling WaitForExit. If we do not wait for these + // methods to be called, its possible to return before we get any data from the process. + redirectInitiated.Wait(); + redirectInitiated.Dispose(); + redirectInitiated = null; + + process.WaitForExit(); + var result = new ProcessResult( + process, + process.ExitCode, + new ReadOnlyCollection(outputLines), + new ReadOnlyCollection(errorLines)); + tcs.TrySetResult(result); + }); + }; + + _ = cancellationToken.Register(() => + { + if (tcs.TrySetCanceled()) + { + // If the underlying process is still running, we should kill it + if (!process.HasExited) + { + try + { + // This will cause Exited to be fired if it already hasn't, ensuring redirectInitiated + // is still disposed even on the cancellation path. + process.Kill(); + } + catch (InvalidOperationException) + { + // Ignore, since the process is already dead + } + } + } + }); + + process.Start(); + onProcessStartHandler?.Invoke(process); + + if (lowPriority) + { + process.PriorityClass = ProcessPriorityClass.BelowNormal; + } + + if (processStartInfo.RedirectStandardOutput) + { + process.BeginOutputReadLine(); + } + + if (processStartInfo.RedirectStandardError) + { + process.BeginErrorReadLine(); + } + + redirectInitiated.Set(); + + return new ProcessInfo(process, processStartInfo, tcs.Task); + } + + public static ProcessStartInfo CreateProcessStartInfo( + string executable, + string arguments, + string? workingDirectory = null, + bool captureOutput = false, + bool displayWindow = true, + Dictionary? environmentVariables = null) + { + var processStartInfo = new ProcessStartInfo(executable, arguments); + + if (!string.IsNullOrEmpty(workingDirectory)) + { + processStartInfo.WorkingDirectory = workingDirectory; + } + + if (environmentVariables != null) + { + foreach (var pair in environmentVariables) + { + processStartInfo.EnvironmentVariables[pair.Key] = pair.Value; + } + } + + if (captureOutput) + { + processStartInfo.UseShellExecute = false; + processStartInfo.RedirectStandardOutput = true; + processStartInfo.RedirectStandardError = true; + } + else + { + processStartInfo.CreateNoWindow = !displayWindow; + processStartInfo.UseShellExecute = displayWindow; + } + + return processStartInfo; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Utilities/SourceFileMatcher.cs b/src/BuiltInTools/dotnet-format/Utilities/SourceFileMatcher.cs new file mode 100644 index 000000000000..1184eeb6d286 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Utilities/SourceFileMatcher.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.Extensions.FileSystemGlobbing; + +namespace Microsoft.CodeAnalysis.Tools.Utilities +{ + internal sealed class SourceFileMatcher + { + private static string[] AllFilesList => new[] { @"**/*.*" }; + + public static SourceFileMatcher CreateMatcher(string[] include, string[] exclude) + => new SourceFileMatcher(include, exclude); + + private readonly Matcher _matcher = new Matcher(StringComparison.OrdinalIgnoreCase); + private readonly bool _shouldMatchAll; + + public ImmutableArray Include { get; } + public ImmutableArray Exclude { get; } + + private SourceFileMatcher(string[] include, string[] exclude) + { + _shouldMatchAll = include.Length == 0 && exclude.Length == 0; + + Include = include.Length > 0 + ? include.ToImmutableArray() + : AllFilesList.ToImmutableArray(); + Exclude = exclude.ToImmutableArray(); + + _matcher = new Matcher(StringComparison.OrdinalIgnoreCase); + _matcher.AddIncludePatterns(Include); + _matcher.AddExcludePatterns(Exclude); + } + + public bool HasMatches(string filePath) + => _shouldMatchAll || _matcher.Match(filePath).HasMatches; + + public IEnumerable GetResultsInFullPath(string directoryPath) + => _matcher.GetResultsInFullPath(directoryPath); + } +} diff --git a/src/BuiltInTools/dotnet-format/WorkspaceFormatResult.cs b/src/BuiltInTools/dotnet-format/WorkspaceFormatResult.cs new file mode 100644 index 000000000000..b66d8bf99e95 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/WorkspaceFormatResult.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Tools +{ + internal class WorkspaceFormatResult + { + public int ExitCode { get; } + public int FilesFormatted { get; } + public int FileCount { get; } + + public WorkspaceFormatResult(int filesFormatted, int fileCount, int exitCode) + { + FilesFormatted = filesFormatted; + FileCount = fileCount; + ExitCode = exitCode; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/WorkspaceType.cs b/src/BuiltInTools/dotnet-format/WorkspaceType.cs new file mode 100644 index 000000000000..2069a22d8454 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/WorkspaceType.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Tools +{ + internal enum WorkspaceType + { + Folder, + Project, + Solution + } +} diff --git a/src/BuiltInTools/dotnet-format/Workspaces/FolderWorkspace.cs b/src/BuiltInTools/dotnet-format/Workspaces/FolderWorkspace.cs new file mode 100644 index 000000000000..6f83bc43235c --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Workspaces/FolderWorkspace.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Tools.Utilities; + +namespace Microsoft.CodeAnalysis.Tools.Workspaces +{ + internal sealed partial class FolderWorkspace : Workspace + { + private static Encoding DefaultEncoding => new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + + private FolderWorkspace(HostServices hostServices) + : base(hostServices, "Folder") + { + } + + public static FolderWorkspace Create() + { + return Create(MefHostServices.DefaultHost); + } + + public static FolderWorkspace Create(HostServices hostServices) + { + return new FolderWorkspace(hostServices); + } + + public Solution OpenFolder(string folderPath, SourceFileMatcher fileMatcher) + { + if (string.IsNullOrEmpty(folderPath) || !Directory.Exists(folderPath)) + { + throw new ArgumentException($"Folder '{folderPath}' does not exist.", nameof(folderPath)); + } + + ClearSolution(); + + var solutionInfo = FolderSolutionLoader.LoadSolutionInfo(folderPath, fileMatcher); + + OnSolutionAdded(solutionInfo); + + return CurrentSolution; + } + + public override bool CanApplyChange(ApplyChangesKind feature) + { + // Whitespace formatting should only produce document changes; no other types of changes are supported. + return feature == ApplyChangesKind.ChangeDocument; + } + + protected override void ApplyDocumentTextChanged(DocumentId documentId, SourceText text) + { + var document = CurrentSolution.GetDocument(documentId); + if (document?.FilePath != null && text.Encoding != null) + { + SaveDocumentText(documentId, document.FilePath, text, text.Encoding); + OnDocumentTextChanged(documentId, text, PreservationMode.PreserveValue); + } + } + + private void SaveDocumentText(DocumentId id, string fullPath, SourceText newText, Encoding encoding) + { + try + { + using var writer = new StreamWriter(fullPath, append: false, encoding); + newText.Write(writer); + } + catch (IOException exception) + { + OnWorkspaceFailed(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, exception.Message, id)); + } + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Workspaces/FolderWorkspace_CSharpProjectLoader.cs b/src/BuiltInTools/dotnet-format/Workspaces/FolderWorkspace_CSharpProjectLoader.cs new file mode 100644 index 000000000000..de19b035f1a5 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Workspaces/FolderWorkspace_CSharpProjectLoader.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Tools.Workspaces +{ + internal sealed partial class FolderWorkspace : Workspace + { + private sealed class CSharpProjectLoader : ProjectLoader + { + public override string Language => LanguageNames.CSharp; + public override string FileExtension => ".cs"; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Workspaces/FolderWorkspace_FolderSolutionLoader.cs b/src/BuiltInTools/dotnet-format/Workspaces/FolderWorkspace_FolderSolutionLoader.cs new file mode 100644 index 000000000000..7e2e2e468fa8 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Workspaces/FolderWorkspace_FolderSolutionLoader.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Tools.Utilities; + +namespace Microsoft.CodeAnalysis.Tools.Workspaces +{ + internal sealed partial class FolderWorkspace : Workspace + { + private static class FolderSolutionLoader + { + private static ImmutableArray ProjectLoaders + => ImmutableArray.Create(new CSharpProjectLoader(), new VisualBasicProjectLoader()); + + public static SolutionInfo LoadSolutionInfo(string folderPath, SourceFileMatcher fileMatcher) + { + var absoluteFolderPath = Path.GetFullPath(folderPath, Directory.GetCurrentDirectory()); + + var filePaths = GetMatchingFilePaths(absoluteFolderPath, fileMatcher); + var editorConfigPaths = EditorConfigFinder.GetEditorConfigPaths(folderPath); + + var projectInfos = ImmutableArray.CreateBuilder(ProjectLoaders.Length); + + // Create projects for each of the supported languages. + foreach (var loader in ProjectLoaders) + { + var projectInfo = loader.LoadProjectInfo(folderPath, filePaths, editorConfigPaths); + if (projectInfo is null) + { + continue; + } + + projectInfos.Add(projectInfo); + } + + // Construct workspace from loaded project infos. + return SolutionInfo.Create( + SolutionId.CreateNewId(debugName: absoluteFolderPath), + version: default, + absoluteFolderPath, + projectInfos); + } + + private static ImmutableArray GetMatchingFilePaths(string folderPath, SourceFileMatcher fileMatcher) + { + // If only file paths were given to be included, then avoid matching against all + // the files beneath the folderPath and instead check if the specified files exist. + if (fileMatcher.Exclude.IsDefaultOrEmpty && AreAllFilePaths(fileMatcher.Include)) + { + return ValidateFilePaths(folderPath, fileMatcher.Include); + } + + return fileMatcher.GetResultsInFullPath(folderPath).ToImmutableArray(); + + static bool AreAllFilePaths(ImmutableArray globs) + { + for (var index = 0; index < globs.Length; index++) + { + // The FileSystemGlobbing.Matcher only supports the '*' wildcard and paths + // ending in a directory separator are treated as folder paths. + if (globs[index].Contains('*') || + globs[index].EndsWith('\\') || + globs[index].EndsWith('/')) + { + return false; + } + } + + return true; + } + + static ImmutableArray ValidateFilePaths(string folderPath, ImmutableArray paths) + { + var filePaths = ImmutableArray.CreateBuilder(paths.Length); + for (var index = 0; index < paths.Length; index++) + { + var filePath = Path.GetFullPath(paths[index], folderPath); + if (File.Exists(filePath)) + { + filePaths.Add(filePath); + } + } + + return filePaths.ToImmutable(); + } + } + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Workspaces/FolderWorkspace_ProjectLoader.cs b/src/BuiltInTools/dotnet-format/Workspaces/FolderWorkspace_ProjectLoader.cs new file mode 100644 index 000000000000..1ad193f08cf5 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Workspaces/FolderWorkspace_ProjectLoader.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.Tools.Workspaces +{ + internal sealed partial class FolderWorkspace : Workspace + { + private abstract class ProjectLoader + { + public abstract string Language { get; } + public abstract string FileExtension { get; } + public virtual string ProjectName => $"{Language}{FileExtension}proj"; + + public virtual ProjectInfo? LoadProjectInfo(string folderPath, ImmutableArray filePaths, ImmutableArray editorConfigPaths) + { + var projectFilePaths = ImmutableArray.CreateBuilder(filePaths.Length); + for (var index = 0; index < filePaths.Length; index++) + { + if (filePaths[index].EndsWith(FileExtension, StringComparison.InvariantCulture)) + { + projectFilePaths.Add(filePaths[index]); + } + } + + if (projectFilePaths.Count == 0) + { + return null; + } + + var projectId = ProjectId.CreateNewId(debugName: folderPath); + + return ProjectInfo.Create( + projectId, + version: default, + name: ProjectName, + assemblyName: folderPath, + Language, + filePath: folderPath, + documents: LoadDocuments(projectId, projectFilePaths.ToImmutable())) + .WithAnalyzerConfigDocuments(LoadDocuments(projectId, editorConfigPaths)); + + static IEnumerable LoadDocuments(ProjectId projectId, ImmutableArray filePaths) + { + var documents = new DocumentInfo[filePaths.Length]; + for (var index = 0; index < filePaths.Length; index++) + { + documents[index] = DocumentInfo.Create( + DocumentId.CreateNewId(projectId, debugName: filePaths[index]), + name: filePaths[index], + loader: new FileTextLoader(filePaths[index], DefaultEncoding), + filePath: filePaths[index]); + } + + return documents; + } + } + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Workspaces/FolderWorkspace_VisualBasicProjectLoader.cs b/src/BuiltInTools/dotnet-format/Workspaces/FolderWorkspace_VisualBasicProjectLoader.cs new file mode 100644 index 000000000000..1f50e45af634 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Workspaces/FolderWorkspace_VisualBasicProjectLoader.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Tools.Workspaces +{ + internal sealed partial class FolderWorkspace : Workspace + { + private sealed class VisualBasicProjectLoader : ProjectLoader + { + public override string Language => LanguageNames.VisualBasic; + public override string FileExtension => ".vb"; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Workspaces/MSBuildWorkspaceFinder.cs b/src/BuiltInTools/dotnet-format/Workspaces/MSBuildWorkspaceFinder.cs new file mode 100644 index 000000000000..f654dade542c --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Workspaces/MSBuildWorkspaceFinder.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +// Original License: +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// See https://github.com/aspnet/DotNetTools/blob/261b27b70027871143540af10a5cba57ce07ff97/src/dotnet-watch/Internal/MsBuildProjectFinder.cs + +namespace Microsoft.CodeAnalysis.Tools.Workspaces +{ + internal class MSBuildWorkspaceFinder + { + // Used to exclude dnx projects + private const string DnxProjectExtension = ".xproj"; + + /// + /// Finds a compatible MSBuild project or solution. + /// The base directory to search + /// A specific project or solution file to find + /// + public static (bool isSolution, string workspacePath) FindWorkspace(string searchDirectory, string? workspacePath = null) + { + if (!string.IsNullOrEmpty(workspacePath)) + { + if (!Path.IsPathRooted(workspacePath)) + { + workspacePath = Path.GetFullPath(workspacePath, searchDirectory); + } + + return Directory.Exists(workspacePath) + ? FindWorkspace(workspacePath!) // IsNullOrEmpty is not annotated on .NET Core 2.1 + : FindFile(workspacePath!); // IsNullOrEmpty is not annotated on .NET Core 2.1 + } + + var foundSolution = FindMatchingFile(searchDirectory, FindSolutionFiles, Resources.Multiple_MSBuild_solution_files_found_in_0_Specify_which_to_use_with_the_workspace_argument); + var foundProject = FindMatchingFile(searchDirectory, FindProjectFiles, Resources.Multiple_MSBuild_project_files_found_in_0_Specify_which_to_use_with_the_workspace_argument); + + if (!string.IsNullOrEmpty(foundSolution) && !string.IsNullOrEmpty(foundProject)) + { + throw new FileNotFoundException(string.Format(Resources.Both_a_MSBuild_project_file_and_solution_file_found_in_0_Specify_which_to_use_with_the_workspace_argument, searchDirectory)); + } + + if (!string.IsNullOrEmpty(foundSolution)) + { + return (true, foundSolution!); // IsNullOrEmpty is not annotated on .NET Core 2.1 + } + + if (!string.IsNullOrEmpty(foundProject)) + { + return (false, foundProject!); // IsNullOrEmpty is not annotated on .NET Core 2.1 + } + + throw new FileNotFoundException(string.Format(Resources.Could_not_find_a_MSBuild_project_or_solution_file_in_0_Specify_which_to_use_with_the_workspace_argument, searchDirectory)); + } + + private static (bool isSolution, string workspacePath) FindFile(string workspacePath) + { + var workspaceExtension = Path.GetExtension(workspacePath); + var isSolution = workspaceExtension.Equals(".sln", StringComparison.OrdinalIgnoreCase) || workspaceExtension.Equals(".slnf", StringComparison.OrdinalIgnoreCase); + var isProject = !isSolution + && workspaceExtension.EndsWith("proj", StringComparison.OrdinalIgnoreCase) + && !workspaceExtension.Equals(DnxProjectExtension, StringComparison.OrdinalIgnoreCase); + + if (!isSolution && !isProject) + { + throw new FileNotFoundException(string.Format(Resources.The_file_0_does_not_appear_to_be_a_valid_project_or_solution_file, Path.GetFileName(workspacePath))); + } + + if (!File.Exists(workspacePath)) + { + var message = isSolution + ? Resources.The_solution_file_0_does_not_exist + : Resources.The_project_file_0_does_not_exist; + throw new FileNotFoundException(string.Format(message, workspacePath)); + } + + return (isSolution, workspacePath); + } + + private static IEnumerable FindSolutionFiles(string basePath) => Directory.EnumerateFileSystemEntries(basePath, "*.sln", SearchOption.TopDirectoryOnly) + .Concat(Directory.EnumerateFileSystemEntries(basePath, "*.slnf", SearchOption.TopDirectoryOnly)); + + private static IEnumerable FindProjectFiles(string basePath) => Directory.EnumerateFileSystemEntries(basePath, "*.*proj", SearchOption.TopDirectoryOnly) + .Where(f => !DnxProjectExtension.Equals(Path.GetExtension(f), StringComparison.OrdinalIgnoreCase)); + + private static string? FindMatchingFile(string searchBase, Func> fileSelector, string multipleFilesFoundError) + { + if (!Directory.Exists(searchBase)) + { + return null; + } + + var files = fileSelector(searchBase).ToList(); + if (files.Count > 1) + { + throw new FileNotFoundException(string.Format(multipleFilesFoundError, searchBase)); + } + + return files.Count == 1 + ? files[0] + : null; + } + } +} diff --git a/src/BuiltInTools/dotnet-format/Workspaces/MSBuildWorkspaceLoader.cs b/src/BuiltInTools/dotnet-format/Workspaces/MSBuildWorkspaceLoader.cs new file mode 100644 index 000000000000..1752dae6281a --- /dev/null +++ b/src/BuiltInTools/dotnet-format/Workspaces/MSBuildWorkspaceLoader.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.MSBuild; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Workspaces +{ + internal static class MSBuildWorkspaceLoader + { + // Used in tests for locking around MSBuild invocations + internal static readonly SemaphoreSlim Guard = new SemaphoreSlim(1, 1); + + public static async Task LoadAsync( + string solutionOrProjectPath, + WorkspaceType workspaceType, + string? binaryLogPath, + bool logWorkspaceWarnings, + ILogger logger, + CancellationToken cancellationToken) + { + var properties = new Dictionary(StringComparer.Ordinal) + { + // This property ensures that XAML files will be compiled in the current AppDomain + // rather than a separate one. Any tasks isolated in AppDomains or tasks that create + // AppDomains will likely not work due to https://github.com/Microsoft/MSBuildLocator/issues/16. + { "AlwaysCompileMarkupFilesInSeparateDomain", bool.FalseString }, + }; + + var workspace = MSBuildWorkspace.Create(properties); + + Build.Framework.ILogger? binlog = null; + if (binaryLogPath is not null) + { + binlog = new Build.Logging.BinaryLogger() + { + Parameters = binaryLogPath, + Verbosity = Build.Framework.LoggerVerbosity.Diagnostic, + }; + } + + if (workspaceType == WorkspaceType.Solution) + { + await workspace.OpenSolutionAsync(solutionOrProjectPath, msbuildLogger: binlog, cancellationToken: cancellationToken).ConfigureAwait(false); + } + else + { + try + { + await workspace.OpenProjectAsync(solutionOrProjectPath, msbuildLogger: binlog, cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch (InvalidOperationException) + { + logger.LogError(Resources.Could_not_format_0_Format_currently_supports_only_CSharp_and_Visual_Basic_projects, solutionOrProjectPath); + workspace.Dispose(); + return null; + } + } + + LogWorkspaceDiagnostics(logger, logWorkspaceWarnings, workspace.Diagnostics); + + return workspace; + + static void LogWorkspaceDiagnostics(ILogger logger, bool logWorkspaceWarnings, ImmutableList diagnostics) + { + if (!logWorkspaceWarnings) + { + if (!diagnostics.IsEmpty) + { + logger.LogWarning(Resources.Warnings_were_encountered_while_loading_the_workspace_Set_the_verbosity_option_to_the_diagnostic_level_to_log_warnings); + } + + return; + } + + foreach (var diagnostic in diagnostics) + { + if (diagnostic.Kind == WorkspaceDiagnosticKind.Failure) + { + logger.LogError(diagnostic.Message); + } + else + { + logger.LogWarning(diagnostic.Message); + } + } + } + } + } +} diff --git a/src/BuiltInTools/dotnet-format/dotnet-format.csproj b/src/BuiltInTools/dotnet-format/dotnet-format.csproj new file mode 100644 index 000000000000..14323f64e97a --- /dev/null +++ b/src/BuiltInTools/dotnet-format/dotnet-format.csproj @@ -0,0 +1,63 @@ + + + + $(NetCurrent) + Exe + Microsoft.CodeAnalysis.Tools + true + Enable + $(NoWarn);8002 + Command line tool for formatting C# and Visual Basic code files based on .editorconfig settings. + LatestMajor + + true + true + Icon.png + $(MSBuildThisFileDirectory)Resources\icon.png + + + true + + + win-x64;win-x86;osx-x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BuiltInTools/dotnet-format/xlf/Resources.cs.xlf b/src/BuiltInTools/dotnet-format/xlf/Resources.cs.xlf new file mode 100644 index 000000000000..1423925215b5 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/xlf/Resources.cs.xlf @@ -0,0 +1,417 @@ + + + + + + A list of relative file or folder paths to exclude from formatting. + Seznam relativních cest k souborům nebo složkám, které se nemají formátovat + + + + A list of relative file or folder paths to include in formatting. All files are formatted if empty. + Seznam relativních cest k souborům nebo složkám, které se mají formátovat. Pokud bude seznam prázdný, budou se formátovat všechny soubory. + + + + A path to a solution file, a project file, or a folder containing a solution or project file. If a path is not specified then the current directory is used. + Cesta k souboru řešení, souboru projektu nebo složce obsahující soubor řešení nebo projektu. Pokud cesta není zadaná, použije se aktuální adresář. + + + + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + + + + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + + + + Accepts a file path, which if provided, will produce a json report in the given directory. + Přijímá cestu k souboru. Pokud se zadá, vytvoří se v daném adresáři sestava JSON. + + + + Accepts a file path which if provided will produce a json report in the given directory. + Accepts a file path which if provided will produce a json report in the given directory. + + + + Analysis complete in {0}ms. + Analýza se dokončila za {0} ms. + + + + Analyzer Reference + Odkaz na analyzátor + + + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the <workspace> argument. + {0} obsahuje jak soubor projektu MSBuild, tak soubor řešení. Určete, který soubor chcete použít, pomocí argumentu <workspace>. + + + + Cannot specify the '--folder' option when fixing style. + Cannot specify the '--folder' option when fixing style. + + + + Cannot specify the '--folder' option when running analyzers. + Cannot specify the '--folder' option when running analyzers. + + + + Cannot specify the '--folder' option with '--no-restore'. + Cannot specify the '--folder' option with '--no-restore'. + + + + Cannot specify the '--folder' option when writing a binary log. + Cannot specify the '--folder' option when writing a binary log. + + + + Code Style + Styl kódu + + + + Complete in {0}ms. + Dokončeno za {0} ms + + + + Could not find a MSBuild project file or solution file in '{0}'. Specify which to use with the <workspace> argument. + V adresáři {0} nešlo najít soubor projektu MSBuild nebo soubor řešení. Určete, který soubor chcete použít, pomocí argumentu <workspace>. + + + + Could not format '{0}'. Format currently supports only C# and Visual Basic projects. + {0} nejde formátovat. Formátovat jde v současné době jenom projekty v jazycích C# a Visual Basic. + + + + Delete {0} characters. + Delete {0} characters. + + + + Determining diagnostics... + Zjišťuje se diagnostika... + + + + Determining formattable files. + Určují se soubory, které se dají formátovat. + + + + Doesn't execute an implicit restore before formatting. + Doesn't execute an implicit restore before formatting. + + + + Failed to apply code fix {0} for {1}: {2} + Nepovedlo se použít opravu kódu {0} pro {1}: {2} + + + + Failed to save formatting changes. + Nepodařilo se uložit změny formátování. + + + + Fix end of line marker. + Opravte značku konce řádku. + + + + Fix file encoding. + Opravte kódování souboru. + + + + Fix final newline. + Opravte odřádkování na konci. + + + + Fix imports ordering. + Opravte pořadí importů. + + + + Fix whitespace formatting. + Opravte formátování prázdných znaků. + + + + Fixing diagnostics... + Opravuje se diagnostika... + + + + Format complete in {0}ms. + Formátování se dokončilo za {0} ms. + + + + Format files generated by the SDK. + Format files generated by the SDK. + + + + Formats code to match editorconfig settings. + Formats code to match editorconfig settings. + + + + Formats files without saving changes to disk. Terminates with a non-zero exit code if any files were formatted. + Formátuje soubory bez toho, aby se změny ukládaly na disk. Pokud se nějaké soubory naformátovaly, ukončí se s nenulovým ukončovacím kódem. + + + + Formatted {0} of {1} files. + Naformátovali jsme {0} z {1} souborů. + + + + Formatting code file '{0}'. + Formátuje se soubor kódu {0}. + + + + Formatting code files in workspace '{0}'. + Formátují se soubory kódu v pracovním prostoru {0}. + + + + Include generated code files in formatting operations. + Zahrňte do operací formátování vygenerované soubory kódu. + + + + Insert '{0}'. + Insert '{0}'. + + + + Loading workspace. + Načítá se pracovní prostor. + + + + Log all project or solution load information to a binary log file. + Log all project or solution load information to a binary log file. + + + + Multiple MSBuild project files found in '{0}'. Specify which to use with the <workspace> argument. + {0} obsahuje více souborů projektů MSBuild. Určete, který soubor chcete použít, pomocí argumentu <workspace>. + + + + Multiple MSBuild solution files found in '{0}'. Specify which to use with the <workspace> argument. + {0} obsahuje více souborů řešení MSBuild. Určete, který soubor chcete použít, pomocí argumentu <workspace>. + + + + Project {0} is using configuration from '{1}'. + Project {0} is using configuration from '{1}'. + + + + Replace {0} characters with '{1}'. + Replace {0} characters with '{1}'. + + + + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + + + + Remove unnecessary import. + Remove unnecessary import. + + + + Run 3rd party analyzers and apply fixes. + Run 3rd party analyzers and apply fixes. + + + + Run 3rd party analyzers and apply fixes. + Spustit analyzátory třetích stran a použít opravy + + + + Run code style analyzers and apply fixes. + Spustit analyzátory stylu kódu a použít opravy + + + + Run whitespace formatting. + Run whitespace formatting. + + + + Run whitespace formatting. Run by default when not applying fixes. + Run whitespace formatting. Run by default when not applying fixes. + + + + Running {0} analysis. + Probíhá analýza {0}. + + + + Running {0} analyzers on {1}. + Running {0} analyzers on {1}. + + + + Running formatters. + Spouští se formátování. + + + + Set the verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] + Nastavte úroveň podrobností. Povolené hodnoty jsou: q[uiet], m[inimal], n[ormal], d[etailed] a diag[nostic] + + + + Skipping referenced project '{0}'. + Přeskočí se odkazovaný projekt {0}. + + + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. + Soubor projektu nebo řešení, se kterým se má operace provést. Pokud soubor není zadaný, příkaz ho bude hledat v aktuálním adresáři. + + + + PROJECT | SOLUTION + PROJECT | SOLUTION + + + + Solution {0} has no projects + Řešení {0} nemá žádné projekty. + + + + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + + + + The '--diagnostics' option only applies when fixing style or running analyzers. + The '--diagnostics' option only applies when fixing style or running analyzers. + + + + The dotnet CLI version is '{0}'. + Verze .NET CLI je {0}. + + + + The dotnet format version is '{0}'. + The dotnet format version is '{0}'. + + + + The dotnet runtime version is '{0}'. + The dotnet runtime version is '{0}'. + + + + The file '{0}' does not appear to be a valid project or solution file. + Soubor {0} zřejmě není platný soubor projektu nebo řešení. + + + + The project file '{0}' does not exist. + Soubor projektu {0} neexistuje. + + + + The severity of diagnostics to fix. Allowed values are info, warn, and error. + The severity of diagnostics to fix. Allowed values are info, warn, and error. + + + + The solution file '{0}' does not exist. + Soubor řešení {0} neexistuje. + + + + Formatted code file '{0}'. + Naformátoval se soubor kódu {0}. + + + + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + + + + Unable to fix {0}. Code fix {1} doesn't support Fix All in Solution. + Nepovedlo se opravit {0}. Oprava kódu {1} nepodporuje možnost Opravit vše v řešení. + + + + Unable to fix {0}. Code fix {1} returned an unexpected operation. + Unable to fix {0}. Code fix {1} returned an unexpected operation. + + + + Unable to fix {0}. No associated code fix found. + Unable to fix {0}. No associated code fix found. + + + + Unable to locate MSBuild. Ensure the .NET SDK was installed with the official installer. + Nepovedlo se najít MSBuild. Ujistěte se, že se sada .NET SDK nainstalovala pomocí oficiálního instalačního programu. + + + + Unable to locate dotnet CLI. Ensure that it is on the PATH. + Nepovedlo se najít .NET CLI. Ujistěte se, že se nachází v proměnné PATH. + + + + Unable to organize imports for '{0}'. The document is too complex. + Nepovedlo se uspořádat importy pro {0}. Dokument je příliš složitý. + + + + Using MSBuild.exe located in '{0}'. + Používá se MSBuild.exe umístěný v {0}. + + + + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + + + + Warnings were encountered while loading the workspace. Set the verbosity option to the 'diagnostic' level to log warnings. + Při načítání pracovního prostoru se vygenerovala upozornění. Pokud chcete upozornění protokolovat, nastavte možnost verbosity na úroveň diagnostic. + + + + Whether to treat the `<workspace>` argument as a simple folder of files. + Určuje, jestli se má argument <workspace> považovat za jednoduchou složku souborů. + + + + Writing formatting report to: '{0}'. + Sestava formátování se zapisuje do {0}. + + + + + \ No newline at end of file diff --git a/src/BuiltInTools/dotnet-format/xlf/Resources.de.xlf b/src/BuiltInTools/dotnet-format/xlf/Resources.de.xlf new file mode 100644 index 000000000000..7c7bc4bb6259 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/xlf/Resources.de.xlf @@ -0,0 +1,417 @@ + + + + + + A list of relative file or folder paths to exclude from formatting. + Eine Liste der relativen Datei- oder Ordnerpfade, die von der Formatierung ausgeschlossen werden sollen. + + + + A list of relative file or folder paths to include in formatting. All files are formatted if empty. + Eine Liste der relativen Datei- oder Ordnerpfade, die in die Formatierung einbezogen werden sollen. Wenn diese Liste leer ist, werden alle Dateien formatiert. + + + + A path to a solution file, a project file, or a folder containing a solution or project file. If a path is not specified then the current directory is used. + Ein Pfad zu einer Projektmappendatei, einer Projektdatei oder einem Ordner, die bzw. der eine Projektmappe oder Projektdatei enthält. Wenn kein Pfad angegeben wird, wird das aktuelle Verzeichnis verwendet. + + + + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + + + + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + + + + Accepts a file path, which if provided, will produce a json report in the given directory. + Akzeptiert einen Dateipfad, der bei Bereitstellung einen JSON-Bericht im angegebenen Verzeichnis erstellt. + + + + Accepts a file path which if provided will produce a json report in the given directory. + Accepts a file path which if provided will produce a json report in the given directory. + + + + Analysis complete in {0}ms. + Die Analyse wurde in {0} ms abgeschlossen. + + + + Analyzer Reference + Analysereferenz + + + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the <workspace> argument. + In "{0}" wurden eine MSBuild-Projektdatei und eine Projektmappendatei gefunden. Geben Sie die zu verwendende Datei mit dem <workspace>-Argument an. + + + + Cannot specify the '--folder' option when fixing style. + Cannot specify the '--folder' option when fixing style. + + + + Cannot specify the '--folder' option when running analyzers. + Cannot specify the '--folder' option when running analyzers. + + + + Cannot specify the '--folder' option with '--no-restore'. + Cannot specify the '--folder' option with '--no-restore'. + + + + Cannot specify the '--folder' option when writing a binary log. + Cannot specify the '--folder' option when writing a binary log. + + + + Code Style + Codeformat + + + + Complete in {0}ms. + Abgeschlossen in {0} ms. + + + + Could not find a MSBuild project file or solution file in '{0}'. Specify which to use with the <workspace> argument. + In "{0}" wurde weder eine MSBuild-Projektdatei noch eine Projektmappendatei gefunden. Geben Sie die zu verwendende Datei mit dem <workspace>-Argument an. + + + + Could not format '{0}'. Format currently supports only C# and Visual Basic projects. + "{0}" konnte nicht formatiert werden. Derzeit wird die Formatierung nur für C#- und Visual Basic-Projekte unterstützt. + + + + Delete {0} characters. + Delete {0} characters. + + + + Determining diagnostics... + Diagnose wird ermittelt... + + + + Determining formattable files. + Formatierbare Dateien werden ermittelt. + + + + Doesn't execute an implicit restore before formatting. + Doesn't execute an implicit restore before formatting. + + + + Failed to apply code fix {0} for {1}: {2} + Fehler beim Anwenden der Codekorrektur "{0}" für "{1}": {2} + + + + Failed to save formatting changes. + Fehler beim Speichern von Formatierungsänderungen. + + + + Fix end of line marker. + Korrigieren Sie den Zeilenendemarker. + + + + Fix file encoding. + Korrigieren Sie die Dateicodierung. + + + + Fix final newline. + Korrigieren Sie den letzten Zeilenumbruch. + + + + Fix imports ordering. + Korrigieren Sie die Importreihenfolge. + + + + Fix whitespace formatting. + Korrigieren Sie die Leerraumformatierung. + + + + Fixing diagnostics... + Diagnose wird korrigiert... + + + + Format complete in {0}ms. + Die Formatierung wurde in {0} ms abgeschlossen. + + + + Format files generated by the SDK. + Format files generated by the SDK. + + + + Formats code to match editorconfig settings. + Formats code to match editorconfig settings. + + + + Formats files without saving changes to disk. Terminates with a non-zero exit code if any files were formatted. + Formatiert Dateien ohne Speichern der Änderungen auf dem Datenträger. Wird mit einem Exitcode ungleich Null beendet, wenn Dateien formatiert wurden. + + + + Formatted {0} of {1} files. + {0} von {1} Dateien wurden formatiert. + + + + Formatting code file '{0}'. + Codedatei "{0}" wird formatiert. + + + + Formatting code files in workspace '{0}'. + Codedateien im Arbeitsbereich "{0}" werden formatiert. + + + + Include generated code files in formatting operations. + Hiermit werden generierte Codedateien in Formatierungsvorgänge einbezogen. + + + + Insert '{0}'. + Insert '{0}'. + + + + Loading workspace. + Arbeitsbereich wird geladen. + + + + Log all project or solution load information to a binary log file. + Log all project or solution load information to a binary log file. + + + + Multiple MSBuild project files found in '{0}'. Specify which to use with the <workspace> argument. + In "{0}" wurden mehrere MSBuild-Projektdateien gefunden. Geben Sie die zu verwendende Datei mit dem <workspace>-Argument an. + + + + Multiple MSBuild solution files found in '{0}'. Specify which to use with the <workspace> argument. + In "{0}" wurden mehrere MSBuild-Projektmappendateien gefunden. Geben Sie die zu verwendende Datei mit dem <workspace>-Argument an. + + + + Project {0} is using configuration from '{1}'. + Project {0} is using configuration from '{1}'. + + + + Replace {0} characters with '{1}'. + Replace {0} characters with '{1}'. + + + + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + + + + Remove unnecessary import. + Remove unnecessary import. + + + + Run 3rd party analyzers and apply fixes. + Run 3rd party analyzers and apply fixes. + + + + Run 3rd party analyzers and apply fixes. + Führen Sie Drittanbieter-Analysetools aus, und wenden Sie Korrekturen an. + + + + Run code style analyzers and apply fixes. + Führen Sie Codeformat-Analysetools aus, und wenden Sie Korrekturen an. + + + + Run whitespace formatting. + Run whitespace formatting. + + + + Run whitespace formatting. Run by default when not applying fixes. + Run whitespace formatting. Run by default when not applying fixes. + + + + Running {0} analysis. + Die Analyse für "{0}" wird ausgeführt. + + + + Running {0} analyzers on {1}. + Running {0} analyzers on {1}. + + + + Running formatters. + Formatierer werden ausgeführt. + + + + Set the verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] + Legen Sie den Ausführlichkeitsgrad fest. Zulässige Werte sind "q[uiet]", "m[inimal]", "n[ormal]", "d[etailed]" und "diag[nostic]". + + + + Skipping referenced project '{0}'. + Überspringen von referenziertem Projekt "{0}". + + + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. + Das Projekt oder die Projektmappendatei, die verwendet werden soll. Wenn keine Datei angegeben ist, durchsucht der Befehl das aktuelle Verzeichnis nach einer Datei. + + + + PROJECT | SOLUTION + PROJECT | SOLUTION + + + + Solution {0} has no projects + Die Projektmappe "{0}" enthält keine Projekte. + + + + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + + + + The '--diagnostics' option only applies when fixing style or running analyzers. + The '--diagnostics' option only applies when fixing style or running analyzers. + + + + The dotnet CLI version is '{0}'. + Die dotnet-CLI-Version ist "{0}". + + + + The dotnet format version is '{0}'. + The dotnet format version is '{0}'. + + + + The dotnet runtime version is '{0}'. + The dotnet runtime version is '{0}'. + + + + The file '{0}' does not appear to be a valid project or solution file. + Die Datei "{0}" ist weder ein gültiges Projekt noch eine Projektmappendatei. + + + + The project file '{0}' does not exist. + Die Projektdatei "{0}" ist nicht vorhanden. + + + + The severity of diagnostics to fix. Allowed values are info, warn, and error. + The severity of diagnostics to fix. Allowed values are info, warn, and error. + + + + The solution file '{0}' does not exist. + Die Projektmappendatei "{0}" ist nicht vorhanden. + + + + Formatted code file '{0}'. + Codedatei "{0}" wurde formatiert. + + + + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + + + + Unable to fix {0}. Code fix {1} doesn't support Fix All in Solution. + "{0}" kann nicht korrigiert werden. Die Codekorrektur "{1}" unterstützt nicht das Korrigieren aller Fehler in einer Projektmappe. + + + + Unable to fix {0}. Code fix {1} returned an unexpected operation. + Unable to fix {0}. Code fix {1} returned an unexpected operation. + + + + Unable to fix {0}. No associated code fix found. + Unable to fix {0}. No associated code fix found. + + + + Unable to locate MSBuild. Ensure the .NET SDK was installed with the official installer. + MSBuild wurde nicht gefunden. Stellen Sie sicher, dass das .NET SDK mit dem offiziellen Installationsprogramm installiert wurde. + + + + Unable to locate dotnet CLI. Ensure that it is on the PATH. + Die dotnet-CLI wurde nicht gefunden. Stellen Sie sicher, dass sie sich im Pfad befindet. + + + + Unable to organize imports for '{0}'. The document is too complex. + Importe für "{0}" können nicht organisiert werden. Das Dokument ist zu komplex. + + + + Using MSBuild.exe located in '{0}'. + MSBuild.exe in "{0}" wird verwendet. + + + + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + + + + Warnings were encountered while loading the workspace. Set the verbosity option to the 'diagnostic' level to log warnings. + Beim Laden des Arbeitsbereichs sind Warnungen aufgetreten. Legen Sie die Ausführlichkeitsoption auf "Diagnose" fest, um Warnungen zu protokollieren. + + + + Whether to treat the `<workspace>` argument as a simple folder of files. + Gibt an, ob das <workspace>-Argument als einfacher Dateiordner behandelt werden soll. + + + + Writing formatting report to: '{0}'. + Formatierungsbericht wird in "{0}" geschrieben. + + + + + \ No newline at end of file diff --git a/src/BuiltInTools/dotnet-format/xlf/Resources.es.xlf b/src/BuiltInTools/dotnet-format/xlf/Resources.es.xlf new file mode 100644 index 000000000000..098f102e48ea --- /dev/null +++ b/src/BuiltInTools/dotnet-format/xlf/Resources.es.xlf @@ -0,0 +1,417 @@ + + + + + + A list of relative file or folder paths to exclude from formatting. + Lista de rutas de acceso de carpeta o archivo relativas que se van a excluir del formato. + + + + A list of relative file or folder paths to include in formatting. All files are formatted if empty. + Lista de rutas de acceso de carpeta o archivo relativas que se van a incluir en el formato. Se da formato a todos los archivos, si están vacíos. + + + + A path to a solution file, a project file, or a folder containing a solution or project file. If a path is not specified then the current directory is used. + Ruta de acceso a un archivo de solución, a un archivo del proyecto o a una carpeta que contiene archivo de proyecto o solución. Si no se especifica ninguna ruta de acceso, se usa el directorio actual. + + + + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + + + + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + + + + Accepts a file path, which if provided, will produce a json report in the given directory. + Acepta una ruta de acceso de archivo que, si se proporciona, generará un informe JSON en el directorio dado. + + + + Accepts a file path which if provided will produce a json report in the given directory. + Accepts a file path which if provided will produce a json report in the given directory. + + + + Analysis complete in {0}ms. + Análisis completado en {0} ms. + + + + Analyzer Reference + Referencia de analizador + + + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the <workspace> argument. + Se encontró un archivo del proyecto y un archivo de solución MSBuild en "{0}". Especifique cuál debe usarse con el argumento <workspace>. + + + + Cannot specify the '--folder' option when fixing style. + Cannot specify the '--folder' option when fixing style. + + + + Cannot specify the '--folder' option when running analyzers. + Cannot specify the '--folder' option when running analyzers. + + + + Cannot specify the '--folder' option with '--no-restore'. + Cannot specify the '--folder' option with '--no-restore'. + + + + Cannot specify the '--folder' option when writing a binary log. + Cannot specify the '--folder' option when writing a binary log. + + + + Code Style + Estilo de código + + + + Complete in {0}ms. + Completar en {0} ms. + + + + Could not find a MSBuild project file or solution file in '{0}'. Specify which to use with the <workspace> argument. + No se encontró ningún archivo del proyecto ni archivo de solución MSBuild en "{0}". Especifique cuál debe usarse con el argumento <workspace>. + + + + Could not format '{0}'. Format currently supports only C# and Visual Basic projects. + No se pudo dar formato a "{0}". El formato admite solo proyectos de C# y Visual Basic en este momento. + + + + Delete {0} characters. + Delete {0} characters. + + + + Determining diagnostics... + Determinando los diagnósticos... + + + + Determining formattable files. + Determinando los archivos formateables. + + + + Doesn't execute an implicit restore before formatting. + Doesn't execute an implicit restore before formatting. + + + + Failed to apply code fix {0} for {1}: {2} + No se pudo aplicar la corrección de código {0} para {1}: {2} + + + + Failed to save formatting changes. + Error al guardar cambios de formato. + + + + Fix end of line marker. + Corrija el marcador de fin de línea. + + + + Fix file encoding. + Corrija la codificación del archivo. + + + + Fix final newline. + Corrija la nueva línea final. + + + + Fix imports ordering. + Corrija el orden de las importaciones. + + + + Fix whitespace formatting. + Corrija el formato de espacio en blanco. + + + + Fixing diagnostics... + Corrigiendo los diagnósticos... + + + + Format complete in {0}ms. + Formato completado en {0} ms. + + + + Format files generated by the SDK. + Format files generated by the SDK. + + + + Formats code to match editorconfig settings. + Formats code to match editorconfig settings. + + + + Formats files without saving changes to disk. Terminates with a non-zero exit code if any files were formatted. + Da formato a los archivos sin guardar los cambios en el disco. Termina con un código de salida distinto de cero si se ha dado formato a algún archivo. + + + + Formatted {0} of {1} files. + {0} de {1} archivos con formato aplicado. + + + + Formatting code file '{0}'. + Formato de archivo de código "{0}". + + + + Formatting code files in workspace '{0}'. + Aplicar formato a archivos de código en espacio de trabajo "{0}". + + + + Include generated code files in formatting operations. + Incluya los archivos de código generados en las operaciones de formato. + + + + Insert '{0}'. + Insert '{0}'. + + + + Loading workspace. + Cargando área de trabajo. + + + + Log all project or solution load information to a binary log file. + Log all project or solution load information to a binary log file. + + + + Multiple MSBuild project files found in '{0}'. Specify which to use with the <workspace> argument. + Se encontraron varios archivos del proyecto MSBuild en "{0}". Especifique cuál debe usarse con el argumento <workspace>. + + + + Multiple MSBuild solution files found in '{0}'. Specify which to use with the <workspace> argument. + Se encontraron varios archivos de solución MSBuild en "{0}". Especifique cuál debe usarse con el argumento <workspace>. + + + + Project {0} is using configuration from '{1}'. + Project {0} is using configuration from '{1}'. + + + + Replace {0} characters with '{1}'. + Replace {0} characters with '{1}'. + + + + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + + + + Remove unnecessary import. + Remove unnecessary import. + + + + Run 3rd party analyzers and apply fixes. + Run 3rd party analyzers and apply fixes. + + + + Run 3rd party analyzers and apply fixes. + Ejecute analizadores de terceros y aplique las correcciones. + + + + Run code style analyzers and apply fixes. + Ejecute analizadores de estilo de código y aplique las correcciones. + + + + Run whitespace formatting. + Run whitespace formatting. + + + + Run whitespace formatting. Run by default when not applying fixes. + Run whitespace formatting. Run by default when not applying fixes. + + + + Running {0} analysis. + Ejecutando el análisis de {0}. + + + + Running {0} analyzers on {1}. + Running {0} analyzers on {1}. + + + + Running formatters. + Ejecutando los formateadores. + + + + Set the verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] + Establecer el nivel de detalle. Los valores permitidos son q[uiet], m[inimal], n[ormal], d[etailed]c y diag[nostic] + + + + Skipping referenced project '{0}'. + Omitiendo projecto al que se hace referencia "{0}". + + + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. + El archivo de proyecto o solución donde operar. Si no se especifica un archivo, el comando buscará uno en el directorio actual. + + + + PROJECT | SOLUTION + PROJECT | SOLUTION + + + + Solution {0} has no projects + La solución {0} no tiene ningún proyecto. + + + + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + + + + The '--diagnostics' option only applies when fixing style or running analyzers. + The '--diagnostics' option only applies when fixing style or running analyzers. + + + + The dotnet CLI version is '{0}'. + La versión de la CLI de dotnet es "{0}". + + + + The dotnet format version is '{0}'. + The dotnet format version is '{0}'. + + + + The dotnet runtime version is '{0}'. + The dotnet runtime version is '{0}'. + + + + The file '{0}' does not appear to be a valid project or solution file. + El archivo "{0}" no parece ser un proyecto o archivo de solución válido. + + + + The project file '{0}' does not exist. + El archivo de proyecto "{0}" no existe. + + + + The severity of diagnostics to fix. Allowed values are info, warn, and error. + The severity of diagnostics to fix. Allowed values are info, warn, and error. + + + + The solution file '{0}' does not exist. + El archivo de solución "{0}" no existe. + + + + Formatted code file '{0}'. + Archivo de código "{0}" con formato aplicado. + + + + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + + + + Unable to fix {0}. Code fix {1} doesn't support Fix All in Solution. + No se puede corregir {0}. La corrección de código {1} no es compatible con la opción para corregir todo en la solución. + + + + Unable to fix {0}. Code fix {1} returned an unexpected operation. + Unable to fix {0}. Code fix {1} returned an unexpected operation. + + + + Unable to fix {0}. No associated code fix found. + Unable to fix {0}. No associated code fix found. + + + + Unable to locate MSBuild. Ensure the .NET SDK was installed with the official installer. + No se encuentra MSBuild. Asegúrese de que el SDK de .NET se haya instalado con el instalador oficial. + + + + Unable to locate dotnet CLI. Ensure that it is on the PATH. + No se encuentra la CLI de dotnet. Asegúrese de que esté en la ruta de acceso (PATH). + + + + Unable to organize imports for '{0}'. The document is too complex. + No se pueden organizar las importaciones para "{0}". El documento es demasiado complejo. + + + + Using MSBuild.exe located in '{0}'. + Se está usando el archivo MSBuild.exe ubicado en "{0}". + + + + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + + + + Warnings were encountered while loading the workspace. Set the verbosity option to the 'diagnostic' level to log warnings. + Se encontraron advertencias al cargar el área de trabajo. Establezca la opción de nivel de detalle en "diagnóstico" para registrar las advertencias. + + + + Whether to treat the `<workspace>` argument as a simple folder of files. + Indica si el argumento "<workspace>" debe tratarse como una simple carpeta de archivos. + + + + Writing formatting report to: '{0}'. + Escribiendo el informe de formato en "{0}". + + + + + \ No newline at end of file diff --git a/src/BuiltInTools/dotnet-format/xlf/Resources.fr.xlf b/src/BuiltInTools/dotnet-format/xlf/Resources.fr.xlf new file mode 100644 index 000000000000..e978b44516f9 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/xlf/Resources.fr.xlf @@ -0,0 +1,417 @@ + + + + + + A list of relative file or folder paths to exclude from formatting. + Liste des chemins relatifs de fichier ou de dossier à exclure de la mise en forme. + + + + A list of relative file or folder paths to include in formatting. All files are formatted if empty. + Liste des chemins relatifs de fichier ou de dossier à inclure dans la mise en forme. Si non renseigné, tous les fichiers sont mis en forme. + + + + A path to a solution file, a project file, or a folder containing a solution or project file. If a path is not specified then the current directory is used. + Chemin d'un fichier solution, d'un fichier projet ou d'un dossier contenant un fichier solution ou un fichier projet. Si aucun chemin n'est spécifié, le répertoire actif est utilisé. + + + + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + + + + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + + + + Accepts a file path, which if provided, will produce a json report in the given directory. + Accepte un chemin de fichier, qui, s'il est fourni, produit un rapport JSON dans le répertoire donné. + + + + Accepts a file path which if provided will produce a json report in the given directory. + Accepts a file path which if provided will produce a json report in the given directory. + + + + Analysis complete in {0}ms. + Analyse effectuée en {0} ms. + + + + Analyzer Reference + Référence de l'analyseur + + + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the <workspace> argument. + Fichier projet et fichier solution MSBuild trouvés dans '{0}'. Spécifiez celui qui doit être utilisé avec l'argument <espace de travail>. + + + + Cannot specify the '--folder' option when fixing style. + Cannot specify the '--folder' option when fixing style. + + + + Cannot specify the '--folder' option when running analyzers. + Cannot specify the '--folder' option when running analyzers. + + + + Cannot specify the '--folder' option with '--no-restore'. + Cannot specify the '--folder' option with '--no-restore'. + + + + Cannot specify the '--folder' option when writing a binary log. + Cannot specify the '--folder' option when writing a binary log. + + + + Code Style + Style de code + + + + Complete in {0}ms. + Effectué en {0} ms. + + + + Could not find a MSBuild project file or solution file in '{0}'. Specify which to use with the <workspace> argument. + Le fichier projet ou le fichier solution MSBuild est introuvable dans '{0}'. Spécifiez celui qui doit être utilisé avec l'argument <espace de travail>. + + + + Could not format '{0}'. Format currently supports only C# and Visual Basic projects. + Impossible de mettre en forme '{0}'. L'opération Mettre en forme prend uniquement en charge les projets C# et Visual Basic pour le moment. + + + + Delete {0} characters. + Delete {0} characters. + + + + Determining diagnostics... + Détermination des diagnostics... + + + + Determining formattable files. + Identification des fichiers pouvant être mis en forme. + + + + Doesn't execute an implicit restore before formatting. + Doesn't execute an implicit restore before formatting. + + + + Failed to apply code fix {0} for {1}: {2} + Échec de l'application du correctif de code {0} pour {1} : {2} + + + + Failed to save formatting changes. + L'enregistrement des changements de mise en forme a échoué. + + + + Fix end of line marker. + Corrigez le marqueur de fin de ligne. + + + + Fix file encoding. + Corrigez l'encodage de fichier. + + + + Fix final newline. + Corrigez la nouvelle ligne de fin. + + + + Fix imports ordering. + Corrigez le classement des importations. + + + + Fix whitespace formatting. + Corrigez la mise en forme des espaces blancs. + + + + Fixing diagnostics... + Correction des diagnostics... + + + + Format complete in {0}ms. + Mise en forme effectuée en {0} ms. + + + + Format files generated by the SDK. + Format files generated by the SDK. + + + + Formats code to match editorconfig settings. + Formats code to match editorconfig settings. + + + + Formats files without saving changes to disk. Terminates with a non-zero exit code if any files were formatted. + Met en forme les fichiers sans enregistrer les changements sur le disque. Se termine par un code de sortie non nul si des fichiers ont été mis en forme. + + + + Formatted {0} of {1} files. + {0} fichiers mis en forme sur {1}. + + + + Formatting code file '{0}'. + Mise en forme du fichier de code '{0}'. + + + + Formatting code files in workspace '{0}'. + Mise en forme des fichiers de code dans l'espace de travail '{0}'. + + + + Include generated code files in formatting operations. + Ajoutez des fichiers de code générés dans les opérations de mise en forme. + + + + Insert '{0}'. + Insert '{0}'. + + + + Loading workspace. + Chargement de l'espace de travail. + + + + Log all project or solution load information to a binary log file. + Log all project or solution load information to a binary log file. + + + + Multiple MSBuild project files found in '{0}'. Specify which to use with the <workspace> argument. + Plusieurs fichiers projet MSBuild trouvés dans '{0}'. Spécifiez celui qui doit être utilisé avec l'argument <espace de travail>. + + + + Multiple MSBuild solution files found in '{0}'. Specify which to use with the <workspace> argument. + Plusieurs fichiers solution MSBuild trouvés dans '{0}'. Spécifiez celui qui doit être utilisé avec l'argument <espace de travail>. + + + + Project {0} is using configuration from '{1}'. + Project {0} is using configuration from '{1}'. + + + + Replace {0} characters with '{1}'. + Replace {0} characters with '{1}'. + + + + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + + + + Remove unnecessary import. + Remove unnecessary import. + + + + Run 3rd party analyzers and apply fixes. + Run 3rd party analyzers and apply fixes. + + + + Run 3rd party analyzers and apply fixes. + Exécutez des analyseurs tiers et appliquez des correctifs. + + + + Run code style analyzers and apply fixes. + Exécutez des analyseurs de style de code et appliquez des correctifs. + + + + Run whitespace formatting. + Run whitespace formatting. + + + + Run whitespace formatting. Run by default when not applying fixes. + Run whitespace formatting. Run by default when not applying fixes. + + + + Running {0} analysis. + Exécution de l'analyse de {0}. + + + + Running {0} analyzers on {1}. + Running {0} analyzers on {1}. + + + + Running formatters. + Exécution des formateurs. + + + + Set the verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] + Définissez le niveau de détail. Les valeurs autorisées sont q[uiet], m[inimal], n[ormal], d[etailed] et diag[nostic] + + + + Skipping referenced project '{0}'. + Saut du projet référencé '{0}'. + + + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. + Fichier projet ou solution à utiliser. Si vous ne spécifiez pas de fichier, la commande en recherche un dans le répertoire actuel. + + + + PROJECT | SOLUTION + PROJECT | SOLUTION + + + + Solution {0} has no projects + La solution {0} n'a aucun projet + + + + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + + + + The '--diagnostics' option only applies when fixing style or running analyzers. + The '--diagnostics' option only applies when fixing style or running analyzers. + + + + The dotnet CLI version is '{0}'. + La version de l'interface CLI dotnet est '{0}'. + + + + The dotnet format version is '{0}'. + The dotnet format version is '{0}'. + + + + The dotnet runtime version is '{0}'. + The dotnet runtime version is '{0}'. + + + + The file '{0}' does not appear to be a valid project or solution file. + Le fichier '{0}' ne semble pas être un fichier projet ou solution valide. + + + + The project file '{0}' does not exist. + Le fichier projet '{0}' n'existe pas. + + + + The severity of diagnostics to fix. Allowed values are info, warn, and error. + The severity of diagnostics to fix. Allowed values are info, warn, and error. + + + + The solution file '{0}' does not exist. + Le fichier solution '{0}' n'existe pas. + + + + Formatted code file '{0}'. + Fichier de code '{0}' mis en forme. + + + + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + + + + Unable to fix {0}. Code fix {1} doesn't support Fix All in Solution. + Impossible de corriger {0}. Le correctif de code {1} ne prend pas en charge la fonctionnalité Tout corriger dans la solution. + + + + Unable to fix {0}. Code fix {1} returned an unexpected operation. + Unable to fix {0}. Code fix {1} returned an unexpected operation. + + + + Unable to fix {0}. No associated code fix found. + Unable to fix {0}. No associated code fix found. + + + + Unable to locate MSBuild. Ensure the .NET SDK was installed with the official installer. + Impossible de localiser MSBuild. Vérifiez que le SDK .NET a été installé avec le programme d'installation officiel. + + + + Unable to locate dotnet CLI. Ensure that it is on the PATH. + Impossible de localiser l'interface CLI dotnet. Vérifiez qu'elle est dans le chemin. + + + + Unable to organize imports for '{0}'. The document is too complex. + Impossible d'organiser les importations pour '{0}'. Le document est trop complexe. + + + + Using MSBuild.exe located in '{0}'. + Utilisation de MSBuild.exe dans '{0}'. + + + + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + + + + Warnings were encountered while loading the workspace. Set the verbosity option to the 'diagnostic' level to log warnings. + Des avertissements ont été rencontrés pendant le chargement de l'espace de travail. Définissez l'option verbosity sur le niveau « diagnostic » pour journaliser les avertissements. + + + + Whether to treat the `<workspace>` argument as a simple folder of files. + Indique si l'argument '<espace de travail>' doit être traité en tant que simple dossier de fichiers. + + + + Writing formatting report to: '{0}'. + Écriture du rapport de mise en forme dans '{0}'. + + + + + \ No newline at end of file diff --git a/src/BuiltInTools/dotnet-format/xlf/Resources.it.xlf b/src/BuiltInTools/dotnet-format/xlf/Resources.it.xlf new file mode 100644 index 000000000000..0cbcb39ae366 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/xlf/Resources.it.xlf @@ -0,0 +1,417 @@ + + + + + + A list of relative file or folder paths to exclude from formatting. + Elenco di percorsi relativi di file o cartelle da escludere dalla formattazione. + + + + A list of relative file or folder paths to include in formatting. All files are formatted if empty. + Elenco di percorsi relativi di file o cartelle da includere nella formattazione. Se è vuoto, tutti i file vengono formattati. + + + + A path to a solution file, a project file, or a folder containing a solution or project file. If a path is not specified then the current directory is used. + Percorso di un file di soluzione, un file di progetto o una cartella contenente una soluzione o un file di progetto. Se non si specifica alcun percorso, viene usata la directory corrente. + + + + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + + + + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + + + + Accepts a file path, which if provided, will produce a json report in the given directory. + Accetta un percorso file che, se specificato, produrrà un report JSON nella directory specificata. + + + + Accepts a file path which if provided will produce a json report in the given directory. + Accepts a file path which if provided will produce a json report in the given directory. + + + + Analysis complete in {0}ms. + L'analisi è stata completata in {0} ms. + + + + Analyzer Reference + Riferimento ad analizzatore + + + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the <workspace> argument. + In '{0}' sono stati trovati sia un file di progetto che un file di soluzione MSBuild. Per specificare quello desiderato, usare l'argomento <workspace>. + + + + Cannot specify the '--folder' option when fixing style. + Cannot specify the '--folder' option when fixing style. + + + + Cannot specify the '--folder' option when running analyzers. + Cannot specify the '--folder' option when running analyzers. + + + + Cannot specify the '--folder' option with '--no-restore'. + Cannot specify the '--folder' option with '--no-restore'. + + + + Cannot specify the '--folder' option when writing a binary log. + Cannot specify the '--folder' option when writing a binary log. + + + + Code Style + Stile codice + + + + Complete in {0}ms. + Completata in {0} ms. + + + + Could not find a MSBuild project file or solution file in '{0}'. Specify which to use with the <workspace> argument. + In '{0}' non è stato possibile trovare alcun file di progetto o di soluzione MSBuild. Per specificare quello desiderato, usare l'argomento <workspace>. + + + + Could not format '{0}'. Format currently supports only C# and Visual Basic projects. + Non è stato possibile formattare '{0}'. Il formato supporta attualmente solo progetti C# e Visual Basic. + + + + Delete {0} characters. + Delete {0} characters. + + + + Determining diagnostics... + Determinazione della diagnostica... + + + + Determining formattable files. + Determinazione dei file formattabili. + + + + Doesn't execute an implicit restore before formatting. + Doesn't execute an implicit restore before formatting. + + + + Failed to apply code fix {0} for {1}: {2} + Non è stato possibile applicare la correzione del codice {0} per {1}: {2} + + + + Failed to save formatting changes. + Non è stato possibile salvare le modifiche di formattazione. + + + + Fix end of line marker. + Correggere il marcatore di fine riga. + + + + Fix file encoding. + Correggere la codifica dei file. + + + + Fix final newline. + Correggere il carattere di nuova riga finale. + + + + Fix imports ordering. + Correggere l'ordinamento delle importazioni. + + + + Fix whitespace formatting. + Correggere la formattazione degli spazi vuoti. + + + + Fixing diagnostics... + Correzione della diagnostica... + + + + Format complete in {0}ms. + La formattazione è stata completata in {0} ms. + + + + Format files generated by the SDK. + Format files generated by the SDK. + + + + Formats code to match editorconfig settings. + Formats code to match editorconfig settings. + + + + Formats files without saving changes to disk. Terminates with a non-zero exit code if any files were formatted. + Formatta i file senza salvare le modifiche nel disco. Termina con un codice di uscita diverso da zero se sono stati formattati file. + + + + Formatted {0} of {1} files. + Sono stati formattati {0} di {1} file. + + + + Formatting code file '{0}'. + Formattazione del file di codice '{0}'. + + + + Formatting code files in workspace '{0}'. + Formattazione del file di codice nell'area di lavoro '{0}'. + + + + Include generated code files in formatting operations. + Includere i file del codice generato nelle operazioni di formattazione. + + + + Insert '{0}'. + Insert '{0}'. + + + + Loading workspace. + Caricamento dell'area di lavoro. + + + + Log all project or solution load information to a binary log file. + Log all project or solution load information to a binary log file. + + + + Multiple MSBuild project files found in '{0}'. Specify which to use with the <workspace> argument. + In '{0}' sono stati trovati più file di progetto MSBuild. Per specificare quello desiderato, usare l'argomento <workspace>. + + + + Multiple MSBuild solution files found in '{0}'. Specify which to use with the <workspace> argument. + In '{0}' sono stati trovati più file di soluzione MSBuild. Per specificare quello desiderato, usare l'argomento <workspace>. + + + + Project {0} is using configuration from '{1}'. + Project {0} is using configuration from '{1}'. + + + + Replace {0} characters with '{1}'. + Replace {0} characters with '{1}'. + + + + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + + + + Remove unnecessary import. + Remove unnecessary import. + + + + Run 3rd party analyzers and apply fixes. + Run 3rd party analyzers and apply fixes. + + + + Run 3rd party analyzers and apply fixes. + Esegue gli analizzatori di terze parti e applica le correzioni. + + + + Run code style analyzers and apply fixes. + Esegue gli analizzatori degli stili di codice e applica le correzioni. + + + + Run whitespace formatting. + Run whitespace formatting. + + + + Run whitespace formatting. Run by default when not applying fixes. + Run whitespace formatting. Run by default when not applying fixes. + + + + Running {0} analysis. + Esecuzione dell'analisi {0}. + + + + Running {0} analyzers on {1}. + Running {0} analyzers on {1}. + + + + Running formatters. + Esecuzione dei formattatori. + + + + Set the verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] + Consente di impostare il livello di dettaglio. I valori consentiti sono q[uiet], m[inimal], n[ormal], d[etailed] e diag[nostic] + + + + Skipping referenced project '{0}'. + Il progetto di riferimento '{0}' verrà ignorato. + + + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. + File di progetto o di soluzione su cui intervenire. Se non si specifica un file, il comando ne cercherà uno nella directory corrente. + + + + PROJECT | SOLUTION + PROJECT | SOLUTION + + + + Solution {0} has no projects + La soluzione {0} non contiene progetti + + + + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + + + + The '--diagnostics' option only applies when fixing style or running analyzers. + The '--diagnostics' option only applies when fixing style or running analyzers. + + + + The dotnet CLI version is '{0}'. + La versione dell'interfaccia della riga di comando di dotnet è '{0}'. + + + + The dotnet format version is '{0}'. + The dotnet format version is '{0}'. + + + + The dotnet runtime version is '{0}'. + The dotnet runtime version is '{0}'. + + + + The file '{0}' does not appear to be a valid project or solution file. + Il file '{0}' non sembra essere un file di progetto o di soluzione valido. + + + + The project file '{0}' does not exist. + Il file di progetto '{0}' non esiste. + + + + The severity of diagnostics to fix. Allowed values are info, warn, and error. + The severity of diagnostics to fix. Allowed values are info, warn, and error. + + + + The solution file '{0}' does not exist. + Il file di soluzione '{0}' non esiste. + + + + Formatted code file '{0}'. + Il file di codice '{0}' è stato formattato. + + + + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + + + + Unable to fix {0}. Code fix {1} doesn't support Fix All in Solution. + Non è possibile correggere {0}. La correzione del codice {1} non supporta l'opzione Correggi tutti nella soluzione. + + + + Unable to fix {0}. Code fix {1} returned an unexpected operation. + Unable to fix {0}. Code fix {1} returned an unexpected operation. + + + + Unable to fix {0}. No associated code fix found. + Unable to fix {0}. No associated code fix found. + + + + Unable to locate MSBuild. Ensure the .NET SDK was installed with the official installer. + Non è possibile individuare MSBuild. Assicurarsi che .NET SDK sia stato installato con il programma di installazione ufficiale. + + + + Unable to locate dotnet CLI. Ensure that it is on the PATH. + Non è possibile individuare l'interfaccia della riga di comando di dotnet. Assicurarsi che sia indicata in PATH. + + + + Unable to organize imports for '{0}'. The document is too complex. + Non è possibile organizzare le importazioni per '{0}'. Il documento è troppo complesso. + + + + Using MSBuild.exe located in '{0}'. + Verrà usata la versione di MSBuild.exe disponibile in '{0}'. + + + + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + + + + Warnings were encountered while loading the workspace. Set the verbosity option to the 'diagnostic' level to log warnings. + Sono stati rilevati avvisi durante il caricamento dell'area di lavoro. Impostare l'opzione del livello di dettaglio su 'diagnostic' per registrare gli avvisi. + + + + Whether to treat the `<workspace>` argument as a simple folder of files. + Indica se considerare l'argomento `<workspace>` come una semplice cartella di file. + + + + Writing formatting report to: '{0}'. + Scrittura del report di formattazione in: '{0}'. + + + + + \ No newline at end of file diff --git a/src/BuiltInTools/dotnet-format/xlf/Resources.ja.xlf b/src/BuiltInTools/dotnet-format/xlf/Resources.ja.xlf new file mode 100644 index 000000000000..f103dba3e358 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/xlf/Resources.ja.xlf @@ -0,0 +1,417 @@ + + + + + + A list of relative file or folder paths to exclude from formatting. + 書式設定から除外するファイルまたはフォルダーの相対パスの一覧。 + + + + A list of relative file or folder paths to include in formatting. All files are formatted if empty. + 書式設定に含めるファイルまたはフォルダーの相対パスの一覧。空の場合、すべてのファイルが書式設定されます。 + + + + A path to a solution file, a project file, or a folder containing a solution or project file. If a path is not specified then the current directory is used. + ソリューション ファイル、プロジェクト ファイル、またはソリューション ファイルかプロジェクト ファイルを含むフォルダーへのパス。パスが指定されていない場合は、現在のディレクトリが使用されます。 + + + + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + + + + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + + + + Accepts a file path, which if provided, will produce a json report in the given directory. + ファイル パスを受け入れると (指定されている場合)、指定されたディレクトリに JSON レポートが生成されます。 + + + + Accepts a file path which if provided will produce a json report in the given directory. + Accepts a file path which if provided will produce a json report in the given directory. + + + + Analysis complete in {0}ms. + {0} ミリ秒で分析が完了します。 + + + + Analyzer Reference + アナライザー参照 + + + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the <workspace> argument. + MSBuild のプロジェクト ファイルとソリューション ファイルの両方が '{0}' で見つかりました。使用するものを <workspace> 引数で指定してください。 + + + + Cannot specify the '--folder' option when fixing style. + Cannot specify the '--folder' option when fixing style. + + + + Cannot specify the '--folder' option when running analyzers. + Cannot specify the '--folder' option when running analyzers. + + + + Cannot specify the '--folder' option with '--no-restore'. + Cannot specify the '--folder' option with '--no-restore'. + + + + Cannot specify the '--folder' option when writing a binary log. + Cannot specify the '--folder' option when writing a binary log. + + + + Code Style + コード スタイル + + + + Complete in {0}ms. + {0} ミリ秒で完了します。 + + + + Could not find a MSBuild project file or solution file in '{0}'. Specify which to use with the <workspace> argument. + '{0}' で MSBuild のプロジェクト ファイルもソリューション ファイルも見つかりませんでした。使用するものを <workspace> 引数で指定してください。 + + + + Could not format '{0}'. Format currently supports only C# and Visual Basic projects. + '{0}' をフォーマットできませんでした。フォーマットは、C# および Visual Basic のプロジェクトでのみサポートされています。 + + + + Delete {0} characters. + Delete {0} characters. + + + + Determining diagnostics... + 診断を決定しています... + + + + Determining formattable files. + 書式設定可能なファイルを判定しています。 + + + + Doesn't execute an implicit restore before formatting. + Doesn't execute an implicit restore before formatting. + + + + Failed to apply code fix {0} for {1}: {2} + {1} のコード修正プログラム {0} を適用できませんでした: {2} + + + + Failed to save formatting changes. + 書式変更を保存できませんでした。 + + + + Fix end of line marker. + 行末マーカーを修正します。 + + + + Fix file encoding. + ファイルのエンコードを修正します。 + + + + Fix final newline. + 最後の改行を修正します。 + + + + Fix imports ordering. + インポートの順序を修正します。 + + + + Fix whitespace formatting. + 空白の書式設定を修正します。 + + + + Fixing diagnostics... + 診断を修正しています... + + + + Format complete in {0}ms. + {0} ミリ秒で書式設定が完了します。 + + + + Format files generated by the SDK. + Format files generated by the SDK. + + + + Formats code to match editorconfig settings. + Formats code to match editorconfig settings. + + + + Formats files without saving changes to disk. Terminates with a non-zero exit code if any files were formatted. + 変更をディスクに保存せずにファイルを書式設定します。いずれかのファイルが書式設定されている場合、0 以外の終了コードで終了します。 + + + + Formatted {0} of {1} files. + {1} 個中 {0} 個のファイルが書式設定されました。 + + + + Formatting code file '{0}'. + コード ファイル '{0}' をフォーマットしています。 + + + + Formatting code files in workspace '{0}'. + ワークスペース '{0}' でコード ファイルを書式設定します。 + + + + Include generated code files in formatting operations. + 生成されたコード ファイルを書式設定操作に含めます。 + + + + Insert '{0}'. + Insert '{0}'. + + + + Loading workspace. + ワークスペースを読み込んでいます。 + + + + Log all project or solution load information to a binary log file. + Log all project or solution load information to a binary log file. + + + + Multiple MSBuild project files found in '{0}'. Specify which to use with the <workspace> argument. + 複数の MSBuild プロジェクト ファイルが '{0}' で見つかりました。使用するものを <workspace> 引数で指定してください。 + + + + Multiple MSBuild solution files found in '{0}'. Specify which to use with the <workspace> argument. + 複数の MSBuild ソリューション ファイルが '{0}' で見つかりました。使用するものを <workspace> 引数で指定してください。 + + + + Project {0} is using configuration from '{1}'. + Project {0} is using configuration from '{1}'. + + + + Replace {0} characters with '{1}'. + Replace {0} characters with '{1}'. + + + + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + + + + Remove unnecessary import. + Remove unnecessary import. + + + + Run 3rd party analyzers and apply fixes. + Run 3rd party analyzers and apply fixes. + + + + Run 3rd party analyzers and apply fixes. + サード パーティのアナライザーを実行し、修正プログラムを適用します。 + + + + Run code style analyzers and apply fixes. + コード スタイル アナライザーを実行し、修正プログラムを適用します。 + + + + Run whitespace formatting. + Run whitespace formatting. + + + + Run whitespace formatting. Run by default when not applying fixes. + Run whitespace formatting. Run by default when not applying fixes. + + + + Running {0} analysis. + {0} 分析を実行しています。 + + + + Running {0} analyzers on {1}. + Running {0} analyzers on {1}. + + + + Running formatters. + フォーマッタを実行しています。 + + + + Set the verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] + 詳細レベルを設定します。許可されている値: q[uiet]、m[inimal]、n[ormal]、d[etailed]、diag[nostic] + + + + Skipping referenced project '{0}'. + 参照プロジェクト '{0}' をスキップしています。 + + + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. + 利用するプロジェクト ファイルまたはソリューション ファイル。指定しない場合、コマンドは現在のディレクトリを検索します。 + + + + PROJECT | SOLUTION + PROJECT | SOLUTION + + + + Solution {0} has no projects + ソリューション {0} にプロジェクトがありません + + + + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + + + + The '--diagnostics' option only applies when fixing style or running analyzers. + The '--diagnostics' option only applies when fixing style or running analyzers. + + + + The dotnet CLI version is '{0}'. + dotnet CLI バージョンは '{0}' です。 + + + + The dotnet format version is '{0}'. + The dotnet format version is '{0}'. + + + + The dotnet runtime version is '{0}'. + The dotnet runtime version is '{0}'. + + + + The file '{0}' does not appear to be a valid project or solution file. + ファイル '{0}' が、有効なプロジェクト ファイルまたはソリューション ファイルではない可能性があります。 + + + + The project file '{0}' does not exist. + プロジェクト ファイル '{0}' が存在しません。 + + + + The severity of diagnostics to fix. Allowed values are info, warn, and error. + The severity of diagnostics to fix. Allowed values are info, warn, and error. + + + + The solution file '{0}' does not exist. + ソリューション ファイル '{0}' が存在しません。 + + + + Formatted code file '{0}'. + コード ファイル '{0}' が書式設定されました。 + + + + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + + + + Unable to fix {0}. Code fix {1} doesn't support Fix All in Solution. + {0} を修復できません。コード修正プログラム {1} は、ソリューションでの [すべて修正] をサポートしていません。 + + + + Unable to fix {0}. Code fix {1} returned an unexpected operation. + Unable to fix {0}. Code fix {1} returned an unexpected operation. + + + + Unable to fix {0}. No associated code fix found. + Unable to fix {0}. No associated code fix found. + + + + Unable to locate MSBuild. Ensure the .NET SDK was installed with the official installer. + MSBuild が見つかりません。.NET SDK が正式なインストーラーでインストールされたことを確認してください。 + + + + Unable to locate dotnet CLI. Ensure that it is on the PATH. + dotnet CLI が見つかりません。PATH 上にあることを確認してください。 + + + + Unable to organize imports for '{0}'. The document is too complex. + '{0}' のインポートを整理できません。ドキュメントが複雑すぎます。 + + + + Using MSBuild.exe located in '{0}'. + '{0}' にある MSBuild.exe を使用しています。 + + + + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + + + + Warnings were encountered while loading the workspace. Set the verbosity option to the 'diagnostic' level to log warnings. + ワークスペースの読み込み中に警告が発生しました。詳細オプションを '診断' レベルに設定して、警告をログに記録してください。 + + + + Whether to treat the `<workspace>` argument as a simple folder of files. + '<workspace>' 引数をファイルの単純なフォルダーとして扱うかどうかを指定します。 + + + + Writing formatting report to: '{0}'. + 書式設定レポートを '{0}' に書き込んでいます。 + + + + + \ No newline at end of file diff --git a/src/BuiltInTools/dotnet-format/xlf/Resources.ko.xlf b/src/BuiltInTools/dotnet-format/xlf/Resources.ko.xlf new file mode 100644 index 000000000000..e36170204414 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/xlf/Resources.ko.xlf @@ -0,0 +1,417 @@ + + + + + + A list of relative file or folder paths to exclude from formatting. + 서식 지정에서 제외할 상대 파일 또는 폴더 경로 목록입니다. + + + + A list of relative file or folder paths to include in formatting. All files are formatted if empty. + 서식 지정에 포함할 상대 파일 또는 폴더 경로 목록입니다. 비어 있으면 모든 파일의 서식이 지정됩니다. + + + + A path to a solution file, a project file, or a folder containing a solution or project file. If a path is not specified then the current directory is used. + 솔루션 파일, 프로젝트 파일이나 솔루션 또는 프로젝트 파일이 포함된 폴더의 경로입니다. 경로를 지정하지 않으면 현재 디렉터리가 사용됩니다. + + + + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + + + + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + + + + Accepts a file path, which if provided, will produce a json report in the given directory. + 제공된 경우 지정된 디렉터리에 json 보고서를 생성할 파일 경로를 허용합니다. + + + + Accepts a file path which if provided will produce a json report in the given directory. + Accepts a file path which if provided will produce a json report in the given directory. + + + + Analysis complete in {0}ms. + {0}밀리초 후 분석이 완료됩니다. + + + + Analyzer Reference + 분석기 참조 + + + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the <workspace> argument. + '{0}'에 MSBuild 프로젝트 파일 및 솔루션 파일이 모두 있습니다. <workspace> 인수를 사용하여 사용할 파일을 지정하세요. + + + + Cannot specify the '--folder' option when fixing style. + Cannot specify the '--folder' option when fixing style. + + + + Cannot specify the '--folder' option when running analyzers. + Cannot specify the '--folder' option when running analyzers. + + + + Cannot specify the '--folder' option with '--no-restore'. + Cannot specify the '--folder' option with '--no-restore'. + + + + Cannot specify the '--folder' option when writing a binary log. + Cannot specify the '--folder' option when writing a binary log. + + + + Code Style + 코드 스타일 + + + + Complete in {0}ms. + {0}ms 후에 완료됩니다. + + + + Could not find a MSBuild project file or solution file in '{0}'. Specify which to use with the <workspace> argument. + '{0}'에서 MSBuild 프로젝트 파일 또는 솔루션 파일을 찾을 수 없습니다. <workspace> 인수를 사용하여 사용할 파일을 지정하세요. + + + + Could not format '{0}'. Format currently supports only C# and Visual Basic projects. + '{0}'의 서식을 지정할 수 없습니다. 서식 지정은 현재 C# 및 Visual Basic 프로젝트만 지원합니다. + + + + Delete {0} characters. + Delete {0} characters. + + + + Determining diagnostics... + 진단을 확인하는 중... + + + + Determining formattable files. + 서식 지정 가능한 파일을 확인하는 중입니다. + + + + Doesn't execute an implicit restore before formatting. + Doesn't execute an implicit restore before formatting. + + + + Failed to apply code fix {0} for {1}: {2} + {1}에 대한 코드 수정 사항 {0}을(를) 적용하지 못했습니다. {2} + + + + Failed to save formatting changes. + 서식 변경 내용을 저장하지 못했습니다. + + + + Fix end of line marker. + 줄의 끝 마커를 수정하세요. + + + + Fix file encoding. + 파일 인코딩을 수정하세요. + + + + Fix final newline. + 최종 줄 바꿈을 수정하세요. + + + + Fix imports ordering. + 가져오기 순서를 수정하세요. + + + + Fix whitespace formatting. + 공백 서식을 수정하세요. + + + + Fixing diagnostics... + 진단을 수정하는 중... + + + + Format complete in {0}ms. + {0}ms 후 서식 지정이 완료됩니다. + + + + Format files generated by the SDK. + Format files generated by the SDK. + + + + Formats code to match editorconfig settings. + Formats code to match editorconfig settings. + + + + Formats files without saving changes to disk. Terminates with a non-zero exit code if any files were formatted. + 디스크에 변경 내용을 저장하지 않고 파일의 서식을 지정합니다. 서식이 지정된 파일이 있는 경우 0이 아닌 종료 코드로 종료됩니다. + + + + Formatted {0} of {1} files. + {1}개 중 {0}개 파일의 서식을 지정했습니다. + + + + Formatting code file '{0}'. + 코드 파일 '{0}'의 서식을 지정합니다. + + + + Formatting code files in workspace '{0}'. + '{0}' 작업 영역에서 코드 파일의 서식을 지정합니다. + + + + Include generated code files in formatting operations. + 서식 지정 작업에서 생성된 코드 파일을 포함합니다. + + + + Insert '{0}'. + Insert '{0}'. + + + + Loading workspace. + 작업 영역을 로드하는 중입니다. + + + + Log all project or solution load information to a binary log file. + Log all project or solution load information to a binary log file. + + + + Multiple MSBuild project files found in '{0}'. Specify which to use with the <workspace> argument. + '{0}'에 여러 MSBuild 프로젝트 파일이 있습니다. <workspace> 인수를 사용하여 사용할 파일을 지정하세요. + + + + Multiple MSBuild solution files found in '{0}'. Specify which to use with the <workspace> argument. + '{0}'에 여러 MSBuild 솔루션 파일이 있습니다. <workspace> 인수를 사용하여 사용할 파일을 지정하세요. + + + + Project {0} is using configuration from '{1}'. + Project {0} is using configuration from '{1}'. + + + + Replace {0} characters with '{1}'. + Replace {0} characters with '{1}'. + + + + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + + + + Remove unnecessary import. + Remove unnecessary import. + + + + Run 3rd party analyzers and apply fixes. + Run 3rd party analyzers and apply fixes. + + + + Run 3rd party analyzers and apply fixes. + 타사 분석기를 실행하고 수정 사항을 적용합니다. + + + + Run code style analyzers and apply fixes. + 코드 스타일 분석기를 실행하고 수정 사항을 적용합니다. + + + + Run whitespace formatting. + Run whitespace formatting. + + + + Run whitespace formatting. Run by default when not applying fixes. + Run whitespace formatting. Run by default when not applying fixes. + + + + Running {0} analysis. + {0} 분석을 실행하고 있습니다. + + + + Running {0} analyzers on {1}. + Running {0} analyzers on {1}. + + + + Running formatters. + 포맷터를 실행하는 중입니다. + + + + Set the verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] + 세부 정보 표시 수준을 설정합니다. 허용되는 값은 q[uiet], m[inimal], n[ormal], d[etailed] 및 diag[nostic]입니다. + + + + Skipping referenced project '{0}'. + 참조된 프로젝트 '{0}'을(를) 건너뜁니다. + + + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. + 수행할 프로젝트 또는 솔루션 파일입니다. 파일을 지정하지 않으면 명령이 현재 디렉토리에서 파일을 검색합니다. + + + + PROJECT | SOLUTION + PROJECT | SOLUTION + + + + Solution {0} has no projects + {0} 솔루션에 프로젝트가 없습니다. + + + + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + + + + The '--diagnostics' option only applies when fixing style or running analyzers. + The '--diagnostics' option only applies when fixing style or running analyzers. + + + + The dotnet CLI version is '{0}'. + dotnet CLI 버전은 '{0}'입니다. + + + + The dotnet format version is '{0}'. + The dotnet format version is '{0}'. + + + + The dotnet runtime version is '{0}'. + The dotnet runtime version is '{0}'. + + + + The file '{0}' does not appear to be a valid project or solution file. + '{0}' 파일은 유효한 프로젝트 또는 솔루션 파일이 아닌 것 같습니다. + + + + The project file '{0}' does not exist. + 프로젝트 파일 '{0}'이(가) 없습니다. + + + + The severity of diagnostics to fix. Allowed values are info, warn, and error. + The severity of diagnostics to fix. Allowed values are info, warn, and error. + + + + The solution file '{0}' does not exist. + 솔루션 파일 '{0}'이(가) 없습니다. + + + + Formatted code file '{0}'. + 코드 파일 '{0}'의 서식을 지정했습니다. + + + + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + + + + Unable to fix {0}. Code fix {1} doesn't support Fix All in Solution. + {0}을(를) 수정할 수 없습니다. 코드 수정 사항 {1}은(는) 솔루션에서 모두 수정을 지원하지 않습니다. + + + + Unable to fix {0}. Code fix {1} returned an unexpected operation. + Unable to fix {0}. Code fix {1} returned an unexpected operation. + + + + Unable to fix {0}. No associated code fix found. + Unable to fix {0}. No associated code fix found. + + + + Unable to locate MSBuild. Ensure the .NET SDK was installed with the official installer. + MSBuild를 찾을 수 없습니다. 공식 설치 관리자를 사용하여 .NET SDK를 설치했는지 확인하세요. + + + + Unable to locate dotnet CLI. Ensure that it is on the PATH. + dotnet CLI를 찾을 수 없습니다. PATH에 있는지 확인하세요. + + + + Unable to organize imports for '{0}'. The document is too complex. + '{0}'에 대한 가져오기를 구성할 수 없습니다. 문서가 너무 복잡합니다. + + + + Using MSBuild.exe located in '{0}'. + '{0}'에 있는 MSBuild.exe를 사용합니다. + + + + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + + + + Warnings were encountered while loading the workspace. Set the verbosity option to the 'diagnostic' level to log warnings. + 작업 영역을 로드하는 동안 경고가 발생했습니다. 경고를 기록하려면 세부 정보 표시 옵션을 '진단' 수준으로 설정하세요. + + + + Whether to treat the `<workspace>` argument as a simple folder of files. + `<workspace>` 인수를 단순 파일 폴더로 처리할지 여부입니다. + + + + Writing formatting report to: '{0}'. + 서식 지정 보고서를 '{0}'에 쓰는 중입니다. + + + + + \ No newline at end of file diff --git a/src/BuiltInTools/dotnet-format/xlf/Resources.pl.xlf b/src/BuiltInTools/dotnet-format/xlf/Resources.pl.xlf new file mode 100644 index 000000000000..718e6a2f9d38 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/xlf/Resources.pl.xlf @@ -0,0 +1,417 @@ + + + + + + A list of relative file or folder paths to exclude from formatting. + Lista względnych ścieżek plików lub folderów, które mają zostać wykluczone z formatowania. + + + + A list of relative file or folder paths to include in formatting. All files are formatted if empty. + Lista względnych ścieżek plików lub folderów do uwzględnienia w formatowaniu. Wszystkie pliki są formatowane, jeśli wartość jest pusta. + + + + A path to a solution file, a project file, or a folder containing a solution or project file. If a path is not specified then the current directory is used. + Ścieżka do pliku rozwiązania, pliku projektu albo folderu zawierającego plik rozwiązania lub plik projektu. Jeśli ścieżka nie zostanie określona, zostanie użyty bieżący katalog. + + + + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + + + + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + + + + Accepts a file path, which if provided, will produce a json report in the given directory. + Akceptuje ścieżkę pliku, która, jeśli zostanie podana, wygeneruje raport JSON w danym katalogu. + + + + Accepts a file path which if provided will produce a json report in the given directory. + Accepts a file path which if provided will produce a json report in the given directory. + + + + Analysis complete in {0}ms. + Analizę ukończono w {0} ms. + + + + Analyzer Reference + Odwołanie do analizatora + + + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the <workspace> argument. + W elemencie „{0}” znaleziono zarówno plik rozwiązania, jak i plik projektu MSBuild. Określ plik do użycia za pomocą argumentu <workspace>. + + + + Cannot specify the '--folder' option when fixing style. + Cannot specify the '--folder' option when fixing style. + + + + Cannot specify the '--folder' option when running analyzers. + Cannot specify the '--folder' option when running analyzers. + + + + Cannot specify the '--folder' option with '--no-restore'. + Cannot specify the '--folder' option with '--no-restore'. + + + + Cannot specify the '--folder' option when writing a binary log. + Cannot specify the '--folder' option when writing a binary log. + + + + Code Style + Styl kodu + + + + Complete in {0}ms. + Wykonaj w {0} ms. + + + + Could not find a MSBuild project file or solution file in '{0}'. Specify which to use with the <workspace> argument. + Nie można znaleźć pliku rozwiązania lub projektu MSBuild w elemencie „{0}”. Określ plik do użycia za pomocą argumentu <workspace>. + + + + Could not format '{0}'. Format currently supports only C# and Visual Basic projects. + Nie można sformatować elementu „{0}”. Obecnie format obsługuje tylko projekty C# i Visual Basic. + + + + Delete {0} characters. + Delete {0} characters. + + + + Determining diagnostics... + Trwa określanie diagnostyki... + + + + Determining formattable files. + Określanie plików, które można formatować. + + + + Doesn't execute an implicit restore before formatting. + Doesn't execute an implicit restore before formatting. + + + + Failed to apply code fix {0} for {1}: {2} + Nie można zastosować poprawki kodu {0} dla elementu {1}: {2} + + + + Failed to save formatting changes. + Nie można zapisać zmian formatowania. + + + + Fix end of line marker. + Napraw znacznik końca wiersza. + + + + Fix file encoding. + Napraw kodowanie pliku. + + + + Fix final newline. + Napraw końcowy nowy wiersz. + + + + Fix imports ordering. + Napraw kolejność importowania. + + + + Fix whitespace formatting. + Napraw formatowanie odstępów. + + + + Fixing diagnostics... + Trwa naprawianie diagnostyki... + + + + Format complete in {0}ms. + Formatowanie zakończono w {0} ms. + + + + Format files generated by the SDK. + Format files generated by the SDK. + + + + Formats code to match editorconfig settings. + Formats code to match editorconfig settings. + + + + Formats files without saving changes to disk. Terminates with a non-zero exit code if any files were formatted. + Formatuje pliki bez zapisywania zmian na dysku. Kończy się niezerowym kodem zakończenia, jeśli zostały sformatowane jakiekolwiek pliki. + + + + Formatted {0} of {1} files. + Sformatowano {0} z {1} plików. + + + + Formatting code file '{0}'. + Formatowanie pliku kodu „{0}”. + + + + Formatting code files in workspace '{0}'. + Formatowanie plików kodu w obszarze roboczym „{0}”. + + + + Include generated code files in formatting operations. + Uwzględnij wygenerowane pliki kodu w operacjach formatowania. + + + + Insert '{0}'. + Insert '{0}'. + + + + Loading workspace. + Ładowanie obszaru roboczego. + + + + Log all project or solution load information to a binary log file. + Log all project or solution load information to a binary log file. + + + + Multiple MSBuild project files found in '{0}'. Specify which to use with the <workspace> argument. + W elemencie „{0}” znaleziono wiele plików projektów MSBuild. Określ, którego użyć, za pomocą argumentu <workspace>. + + + + Multiple MSBuild solution files found in '{0}'. Specify which to use with the <workspace> argument. + W elemencie „{0}” znaleziono wiele plików rozwiązań MSBuild. Określ plik do użycia za pomocą argumentu <workspace>. + + + + Project {0} is using configuration from '{1}'. + Project {0} is using configuration from '{1}'. + + + + Replace {0} characters with '{1}'. + Replace {0} characters with '{1}'. + + + + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + + + + Remove unnecessary import. + Remove unnecessary import. + + + + Run 3rd party analyzers and apply fixes. + Run 3rd party analyzers and apply fixes. + + + + Run 3rd party analyzers and apply fixes. + Uruchom analizatory innych firm i zastosuj poprawki. + + + + Run code style analyzers and apply fixes. + Uruchom analizatory stylu kodu i zastosuj poprawki. + + + + Run whitespace formatting. + Run whitespace formatting. + + + + Run whitespace formatting. Run by default when not applying fixes. + Run whitespace formatting. Run by default when not applying fixes. + + + + Running {0} analysis. + Uruchamianie analizy {0}. + + + + Running {0} analyzers on {1}. + Running {0} analyzers on {1}. + + + + Running formatters. + Uruchamianie elementów formatujących. + + + + Set the verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] + Ustaw poziom szczegółowości. Dozwolone wartości to q[uiet] (cicha), m[inimal] (minimalna), n[ormal] (normalna), d[etailed] (szczegółowa) i diag[nostic] (diagnostyczna) + + + + Skipping referenced project '{0}'. + Pomijanie przywoływanego projektu „{0}”. + + + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. + Plik projektu lub rozwiązania, dla którego ma zostać wykonana operacja. Jeśli plik nie zostanie podany, polecenie wyszuka go w bieżącym katalogu. + + + + PROJECT | SOLUTION + PROJECT | SOLUTION + + + + Solution {0} has no projects + Rozwiązanie {0} nie zawiera projektów + + + + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + + + + The '--diagnostics' option only applies when fixing style or running analyzers. + The '--diagnostics' option only applies when fixing style or running analyzers. + + + + The dotnet CLI version is '{0}'. + Wersja interfejsu wiersza polecenia dotnet to „{0}”. + + + + The dotnet format version is '{0}'. + The dotnet format version is '{0}'. + + + + The dotnet runtime version is '{0}'. + The dotnet runtime version is '{0}'. + + + + The file '{0}' does not appear to be a valid project or solution file. + Plik „{0}” prawdopodobnie nie jest prawidłowym plikiem projektu lub rozwiązania. + + + + The project file '{0}' does not exist. + Plik projektu „{0}” nie istnieje. + + + + The severity of diagnostics to fix. Allowed values are info, warn, and error. + The severity of diagnostics to fix. Allowed values are info, warn, and error. + + + + The solution file '{0}' does not exist. + Plik rozwiązania „{0}” nie istnieje. + + + + Formatted code file '{0}'. + Sformatowano plik kodu „{0}”. + + + + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + + + + Unable to fix {0}. Code fix {1} doesn't support Fix All in Solution. + Nie można naprawić elementu {0}. Poprawka kodu {1} nie obsługuje opcji naprawiania wszystkich wystąpień w rozwiązaniu. + + + + Unable to fix {0}. Code fix {1} returned an unexpected operation. + Unable to fix {0}. Code fix {1} returned an unexpected operation. + + + + Unable to fix {0}. No associated code fix found. + Unable to fix {0}. No associated code fix found. + + + + Unable to locate MSBuild. Ensure the .NET SDK was installed with the official installer. + Nie można zlokalizować programu MSBuild. Upewnij się, że zestaw .NET SDK został zainstalowany przy użyciu oficjalnego instalatora. + + + + Unable to locate dotnet CLI. Ensure that it is on the PATH. + Nie można zlokalizować wiersza polecenia dotnet. Upewnij się, że znajduje się on w ścieżce. + + + + Unable to organize imports for '{0}'. The document is too complex. + Nie można zorganizować importów dla elementu „{0}”. Dokument jest zbyt złożony. + + + + Using MSBuild.exe located in '{0}'. + Używanie pliku MSBuild.exe znajdującego się w lokalizacji „{0}”. + + + + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + + + + Warnings were encountered while loading the workspace. Set the verbosity option to the 'diagnostic' level to log warnings. + Podczas ładowania obszaru roboczego napotkano ostrzeżenia. Ustaw opcję poziomu szczegółowości na wartość „diagnostyka”, aby rejestrować ostrzeżenia. + + + + Whether to treat the `<workspace>` argument as a simple folder of files. + Określa, czy traktować argument „<workspace>” jako prosty folder plików. + + + + Writing formatting report to: '{0}'. + Zapisywanie raportu formatowania w: „{0}”... + + + + + \ No newline at end of file diff --git a/src/BuiltInTools/dotnet-format/xlf/Resources.pt-BR.xlf b/src/BuiltInTools/dotnet-format/xlf/Resources.pt-BR.xlf new file mode 100644 index 000000000000..9a8687ea74c1 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/xlf/Resources.pt-BR.xlf @@ -0,0 +1,417 @@ + + + + + + A list of relative file or folder paths to exclude from formatting. + Uma lista de caminhos de arquivo ou pasta relativos para excluir da formatação. + + + + A list of relative file or folder paths to include in formatting. All files are formatted if empty. + Uma lista de caminhos de arquivo ou pasta relativos para incluir na formatação. Todos os arquivos serão formatados se estiverem vazios. + + + + A path to a solution file, a project file, or a folder containing a solution or project file. If a path is not specified then the current directory is used. + Um caminho para um arquivo de solução, um arquivo de projeto ou uma pasta contendo um arquivo de solução ou de projeto. Se não for especificado um caminho, o diretório atual será usado. + + + + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + + + + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + + + + Accepts a file path, which if provided, will produce a json report in the given directory. + Aceita um caminho de arquivo, que, se fornecido, produzirá um relatório JSON no diretório especificado. + + + + Accepts a file path which if provided will produce a json report in the given directory. + Accepts a file path which if provided will produce a json report in the given directory. + + + + Analysis complete in {0}ms. + Análise concluída em {0} ms. + + + + Analyzer Reference + Referência do Analisador + + + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the <workspace> argument. + Foram encontrados um arquivo de solução e um arquivo de projeto do MSBuild em '{0}'. Especifique qual será usado com o argumento <workspace>. + + + + Cannot specify the '--folder' option when fixing style. + Cannot specify the '--folder' option when fixing style. + + + + Cannot specify the '--folder' option when running analyzers. + Cannot specify the '--folder' option when running analyzers. + + + + Cannot specify the '--folder' option with '--no-restore'. + Cannot specify the '--folder' option with '--no-restore'. + + + + Cannot specify the '--folder' option when writing a binary log. + Cannot specify the '--folder' option when writing a binary log. + + + + Code Style + Estilo do Código + + + + Complete in {0}ms. + Concluir em {0} ms. + + + + Could not find a MSBuild project file or solution file in '{0}'. Specify which to use with the <workspace> argument. + Não foi possível encontrar um arquivo de solução nem um arquivo de projeto do MSBuild em '{0}'. Especifique qual será usado com o argumento <workspace>. + + + + Could not format '{0}'. Format currently supports only C# and Visual Basic projects. + Não foi possível formatar '{0}'. O formato atualmente suporta apenas projetos do Visual Basic e C#. + + + + Delete {0} characters. + Delete {0} characters. + + + + Determining diagnostics... + Determinando os diagnósticos... + + + + Determining formattable files. + Determinando arquivos formatáveis. + + + + Doesn't execute an implicit restore before formatting. + Doesn't execute an implicit restore before formatting. + + + + Failed to apply code fix {0} for {1}: {2} + Falha ao aplicar a correção de código {0} para {1}: {2} + + + + Failed to save formatting changes. + Falha ao salvar alterações de formatação. + + + + Fix end of line marker. + Corrigir o final do marcador de linha. + + + + Fix file encoding. + Corrigir a codificação de arquivo. + + + + Fix final newline. + Corrigir a nova linha final. + + + + Fix imports ordering. + Corrigir a ordenação de importações. + + + + Fix whitespace formatting. + Corrigir a formatação de espaço em branco. + + + + Fixing diagnostics... + Corrigindo os diagnósticos... + + + + Format complete in {0}ms. + Formatação concluída em {0} ms. + + + + Format files generated by the SDK. + Format files generated by the SDK. + + + + Formats code to match editorconfig settings. + Formats code to match editorconfig settings. + + + + Formats files without saving changes to disk. Terminates with a non-zero exit code if any files were formatted. + Formata arquivos sem salvar as alterações no disco. Termina com um código de saída diferente de zero se algum arquivo foi formatado. + + + + Formatted {0} of {1} files. + {0} de {1} arquivos formatados. + + + + Formatting code file '{0}'. + Arquivo de código de formatação '{0}'. + + + + Formatting code files in workspace '{0}'. + Formatação de arquivos de código no espaço de trabalho '{0}'. + + + + Include generated code files in formatting operations. + Incluir arquivos de código gerados em operações de formatação. + + + + Insert '{0}'. + Insert '{0}'. + + + + Loading workspace. + Carregando espaço de trabalho. + + + + Log all project or solution load information to a binary log file. + Log all project or solution load information to a binary log file. + + + + Multiple MSBuild project files found in '{0}'. Specify which to use with the <workspace> argument. + Foram encontrados vários arquivos de projeto do MSBuild em '{0}'. Especifique qual será usado com o argumento <workspace>. + + + + Multiple MSBuild solution files found in '{0}'. Specify which to use with the <workspace> argument. + Foram encontrados vários arquivos de solução do MSBuild em '{0}'. Especifique qual será usado com o argumento <workspace>. + + + + Project {0} is using configuration from '{1}'. + Project {0} is using configuration from '{1}'. + + + + Replace {0} characters with '{1}'. + Replace {0} characters with '{1}'. + + + + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + + + + Remove unnecessary import. + Remove unnecessary import. + + + + Run 3rd party analyzers and apply fixes. + Run 3rd party analyzers and apply fixes. + + + + Run 3rd party analyzers and apply fixes. + Executar analisadores de terceiros e aplicar correções. + + + + Run code style analyzers and apply fixes. + Executar analisadores de estilo de código e aplicar correções. + + + + Run whitespace formatting. + Run whitespace formatting. + + + + Run whitespace formatting. Run by default when not applying fixes. + Run whitespace formatting. Run by default when not applying fixes. + + + + Running {0} analysis. + Executando a análise de {0}. + + + + Running {0} analyzers on {1}. + Running {0} analyzers on {1}. + + + + Running formatters. + Executando formatadores. + + + + Set the verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] + Defina o nível de detalhes. Os valores permitidos são q[uiet], m[inimal], n[ormal], d[etailed], e diag[nostic] + + + + Skipping referenced project '{0}'. + Ignorando o projeto referenciado '{0}'. + + + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. + O arquivo de solução ou projeto para operar. Se um arquivo não for especificado, o comando pesquisará um no diretório atual. + + + + PROJECT | SOLUTION + PROJECT | SOLUTION + + + + Solution {0} has no projects + A solução {0} não tem nenhum projeto + + + + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + + + + The '--diagnostics' option only applies when fixing style or running analyzers. + The '--diagnostics' option only applies when fixing style or running analyzers. + + + + The dotnet CLI version is '{0}'. + A versão do CLI do dotnet é '{0}'. + + + + The dotnet format version is '{0}'. + The dotnet format version is '{0}'. + + + + The dotnet runtime version is '{0}'. + The dotnet runtime version is '{0}'. + + + + The file '{0}' does not appear to be a valid project or solution file. + O arquivo '{0}' parece não ser um projeto válido ou o arquivo de solução. + + + + The project file '{0}' does not exist. + O arquivo de projeto '{0}' não existe. + + + + The severity of diagnostics to fix. Allowed values are info, warn, and error. + The severity of diagnostics to fix. Allowed values are info, warn, and error. + + + + The solution file '{0}' does not exist. + O arquivo de solução '{0}' não existe. + + + + Formatted code file '{0}'. + Arquivo de código '{0}' formatado. + + + + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + + + + Unable to fix {0}. Code fix {1} doesn't support Fix All in Solution. + Não é possível corrigir {0}. A correção de código {1} não dá suporte à opção Corrigir Tudo na Solução. + + + + Unable to fix {0}. Code fix {1} returned an unexpected operation. + Unable to fix {0}. Code fix {1} returned an unexpected operation. + + + + Unable to fix {0}. No associated code fix found. + Unable to fix {0}. No associated code fix found. + + + + Unable to locate MSBuild. Ensure the .NET SDK was installed with the official installer. + Não é possível localizar o MSBuild. Verifique se o SDK do .NET foi instalado com o instalador oficial. + + + + Unable to locate dotnet CLI. Ensure that it is on the PATH. + Não é possível localizar a CLI do dotnet. Verifique se está no CAMINHO. + + + + Unable to organize imports for '{0}'. The document is too complex. + Não é possível organizar importações para '{0}'. O documento é muito complexo. + + + + Using MSBuild.exe located in '{0}'. + Usando MSBuild.exe localizado em '{0}'. + + + + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + + + + Warnings were encountered while loading the workspace. Set the verbosity option to the 'diagnostic' level to log warnings. + Foram encontrados avisos ao carregar o workspace. Defina a opção de detalhamento para o nível de 'diagnóstico' para registrar avisos. + + + + Whether to treat the `<workspace>` argument as a simple folder of files. + Se o argumento `<workspace>` será tratado como uma simples pasta de arquivos. + + + + Writing formatting report to: '{0}'. + Gravando relatório de formatação em '{0}'. + + + + + \ No newline at end of file diff --git a/src/BuiltInTools/dotnet-format/xlf/Resources.ru.xlf b/src/BuiltInTools/dotnet-format/xlf/Resources.ru.xlf new file mode 100644 index 000000000000..8898532273e3 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/xlf/Resources.ru.xlf @@ -0,0 +1,417 @@ + + + + + + A list of relative file or folder paths to exclude from formatting. + Список относительных путей к файлам и папкам, исключаемым из форматирования. + + + + A list of relative file or folder paths to include in formatting. All files are formatted if empty. + Список относительных путей к файлам и папкам, включаемым в форматирование. Если список пуст, форматируются все файлы. + + + + A path to a solution file, a project file, or a folder containing a solution or project file. If a path is not specified then the current directory is used. + Путь к файлу решения, файлу проекта или папке, содержащей файл решения или проекта. Если путь не указан, используется текущий каталог. + + + + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + + + + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + + + + Accepts a file path, which if provided, will produce a json report in the given directory. + Принимает путь к файлу. Если путь к файлу указан, создает отчет JSON в указанном каталоге. + + + + Accepts a file path which if provided will produce a json report in the given directory. + Accepts a file path which if provided will produce a json report in the given directory. + + + + Analysis complete in {0}ms. + Анализ завершен. Время анализа: {0} мс. + + + + Analyzer Reference + Ссылка на анализатор + + + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the <workspace> argument. + В "{0}" обнаружены как файл проекта, так и файл решения MSBuild. Укажите используемый файл с помощью аргумента <workspace>. + + + + Cannot specify the '--folder' option when fixing style. + Cannot specify the '--folder' option when fixing style. + + + + Cannot specify the '--folder' option when running analyzers. + Cannot specify the '--folder' option when running analyzers. + + + + Cannot specify the '--folder' option with '--no-restore'. + Cannot specify the '--folder' option with '--no-restore'. + + + + Cannot specify the '--folder' option when writing a binary log. + Cannot specify the '--folder' option when writing a binary log. + + + + Code Style + Стиль кода + + + + Complete in {0}ms. + Завершено. Время выполнения: {0} мс. + + + + Could not find a MSBuild project file or solution file in '{0}'. Specify which to use with the <workspace> argument. + Не удалось найти файл проекта или решения MSBuild в "{0}". Укажите используемый файл с помощью аргумента <workspace>. + + + + Could not format '{0}'. Format currently supports only C# and Visual Basic projects. + Не удалось отформатировать "{0}". Форматирование сейчас поддерживается только для проектов C# и Visual Basic. + + + + Delete {0} characters. + Delete {0} characters. + + + + Determining diagnostics... + Определение диагностики… + + + + Determining formattable files. + Определение форматируемых файлов. + + + + Doesn't execute an implicit restore before formatting. + Doesn't execute an implicit restore before formatting. + + + + Failed to apply code fix {0} for {1}: {2} + Не удалось применить исправление кода {0} для {1}: {2} + + + + Failed to save formatting changes. + Не удалось сохранить изменения форматирования. + + + + Fix end of line marker. + Исправить маркер конца строки. + + + + Fix file encoding. + Исправить кодировку файла. + + + + Fix final newline. + Исправить завершающий символ новой строки. + + + + Fix imports ordering. + Исправить порядок импорта. + + + + Fix whitespace formatting. + Исправить форматирование пробелов. + + + + Fixing diagnostics... + Исправление диагностики… + + + + Format complete in {0}ms. + Форматирование завершено. Время выполнения: {0} мс. + + + + Format files generated by the SDK. + Format files generated by the SDK. + + + + Formats code to match editorconfig settings. + Formats code to match editorconfig settings. + + + + Formats files without saving changes to disk. Terminates with a non-zero exit code if any files were formatted. + Форматирует файлы без сохранения изменений на диск. Завершается с ненулевым кодом завершения, если были отформатированы какие-либо файлы. + + + + Formatted {0} of {1} files. + Отформатировано файлов: {0} из {1}. + + + + Formatting code file '{0}'. + Форматирование файла кода "{0}". + + + + Formatting code files in workspace '{0}'. + Форматирование кода файлов в рабочей области "{0}". + + + + Include generated code files in formatting operations. + Включить созданные файлы кода в операции форматирования. + + + + Insert '{0}'. + Insert '{0}'. + + + + Loading workspace. + Загрузка рабочей области. + + + + Log all project or solution load information to a binary log file. + Log all project or solution load information to a binary log file. + + + + Multiple MSBuild project files found in '{0}'. Specify which to use with the <workspace> argument. + В "{0}" обнаружено несколько файлов проекта MSBuild. Укажите используемый файл с помощью аргумента <workspace>. + + + + Multiple MSBuild solution files found in '{0}'. Specify which to use with the <workspace> argument. + В "{0}" обнаружено несколько файлов решения MSBuild. Укажите используемый файл с помощью аргумента <workspace>. + + + + Project {0} is using configuration from '{1}'. + Project {0} is using configuration from '{1}'. + + + + Replace {0} characters with '{1}'. + Replace {0} characters with '{1}'. + + + + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + + + + Remove unnecessary import. + Remove unnecessary import. + + + + Run 3rd party analyzers and apply fixes. + Run 3rd party analyzers and apply fixes. + + + + Run 3rd party analyzers and apply fixes. + Запустите сторонние анализаторы и примените исправления. + + + + Run code style analyzers and apply fixes. + Запустите анализаторы стиля кода и примените исправления. + + + + Run whitespace formatting. + Run whitespace formatting. + + + + Run whitespace formatting. Run by default when not applying fixes. + Run whitespace formatting. Run by default when not applying fixes. + + + + Running {0} analysis. + Выполнение анализа {0}. + + + + Running {0} analyzers on {1}. + Running {0} analyzers on {1}. + + + + Running formatters. + Запуск средств форматирования. + + + + Set the verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] + Задает уровень детализации. Допустимые значения: q (без вывода подробных сведений), m (минимальный), n (нормальный), d (подробный) и diag (диагностика) + + + + Skipping referenced project '{0}'. + Пропуск указанного проекта "{0}". + + + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. + Файл проекта или решения. Если файл не указан, команда будет искать его в текущем каталоге. + + + + PROJECT | SOLUTION + PROJECT | SOLUTION + + + + Solution {0} has no projects + Решение {0} не содержит проектов. + + + + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + + + + The '--diagnostics' option only applies when fixing style or running analyzers. + The '--diagnostics' option only applies when fixing style or running analyzers. + + + + The dotnet CLI version is '{0}'. + Версия CLI dotnet: "{0}". + + + + The dotnet format version is '{0}'. + The dotnet format version is '{0}'. + + + + The dotnet runtime version is '{0}'. + The dotnet runtime version is '{0}'. + + + + The file '{0}' does not appear to be a valid project or solution file. + Файл "{0}" не является допустимым файлом проекта или решения. + + + + The project file '{0}' does not exist. + Файл проекта "{0}" не существует. + + + + The severity of diagnostics to fix. Allowed values are info, warn, and error. + The severity of diagnostics to fix. Allowed values are info, warn, and error. + + + + The solution file '{0}' does not exist. + Файл решения "{0}" не существует. + + + + Formatted code file '{0}'. + Отформатированный файл кода "{0}". + + + + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + + + + Unable to fix {0}. Code fix {1} doesn't support Fix All in Solution. + Не удалось исправить {0}. Исправление кода {1} не поддерживает функцию "Исправить все в решении". + + + + Unable to fix {0}. Code fix {1} returned an unexpected operation. + Unable to fix {0}. Code fix {1} returned an unexpected operation. + + + + Unable to fix {0}. No associated code fix found. + Unable to fix {0}. No associated code fix found. + + + + Unable to locate MSBuild. Ensure the .NET SDK was installed with the official installer. + Не удается найти MSBuild. Убедитесь, что пакет SDK для .NET был установлен с официальным установщиком. + + + + Unable to locate dotnet CLI. Ensure that it is on the PATH. + Не удалось найти CLI dotnet. Убедитесь, что путь к нему добавлен в переменную среды PATH. + + + + Unable to organize imports for '{0}'. The document is too complex. + Не удается организовать импорты для "{0}". Слишком сложный документ. + + + + Using MSBuild.exe located in '{0}'. + Используется файл MSBuild.exe, расположенный в каталоге "{0}". + + + + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + + + + Warnings were encountered while loading the workspace. Set the verbosity option to the 'diagnostic' level to log warnings. + При загрузке рабочей области были обнаружены предупреждения. Задайте для параметра уровня детализации значение "Диагностика", чтобы вести журнал предупреждений. + + + + Whether to treat the `<workspace>` argument as a simple folder of files. + Следует ли рассматривать аргумент "<workspace>" как простую папку с файлами. + + + + Writing formatting report to: '{0}'. + Запись отчета о форматировании в "{0}". + + + + + \ No newline at end of file diff --git a/src/BuiltInTools/dotnet-format/xlf/Resources.tr.xlf b/src/BuiltInTools/dotnet-format/xlf/Resources.tr.xlf new file mode 100644 index 000000000000..8ca53319d837 --- /dev/null +++ b/src/BuiltInTools/dotnet-format/xlf/Resources.tr.xlf @@ -0,0 +1,417 @@ + + + + + + A list of relative file or folder paths to exclude from formatting. + Biçimlendirmeden hariç tutulacak göreli dosya veya klasör yollarının listesi. + + + + A list of relative file or folder paths to include in formatting. All files are formatted if empty. + Biçimlendirmeye dahil edilecek göreli dosya veya klasör yollarının listesi. Boşsa, tüm dosyalar biçimlendirilir. + + + + A path to a solution file, a project file, or a folder containing a solution or project file. If a path is not specified then the current directory is used. + Çözüm dosyasının, proje dosyasının veya çözüm ya da proje dosyasını içeren klasörün yolu. Yol belirtilmezse geçerli dizin kullanılır. + + + + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + + + + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + + + + Accepts a file path, which if provided, will produce a json report in the given directory. + Sağlanırsa, verilen dizinde json raporu oluşturacak bir dosya yolunu kabul eder. + + + + Accepts a file path which if provided will produce a json report in the given directory. + Accepts a file path which if provided will produce a json report in the given directory. + + + + Analysis complete in {0}ms. + Analiz {0} ms içinde tamamlandı. + + + + Analyzer Reference + Çözümleyici Başvurusu + + + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the <workspace> argument. + '{0}' içinde hem MSBuild proje dosyası hem de çözüm dosyası bulundu. Hangisinin <çalışma alanı> bağımsız değişkeni ile kullanılacağını belirtin. + + + + Cannot specify the '--folder' option when fixing style. + Cannot specify the '--folder' option when fixing style. + + + + Cannot specify the '--folder' option when running analyzers. + Cannot specify the '--folder' option when running analyzers. + + + + Cannot specify the '--folder' option with '--no-restore'. + Cannot specify the '--folder' option with '--no-restore'. + + + + Cannot specify the '--folder' option when writing a binary log. + Cannot specify the '--folder' option when writing a binary log. + + + + Code Style + Kod Stili + + + + Complete in {0}ms. + {0} ms içinde tamamlayın. + + + + Could not find a MSBuild project file or solution file in '{0}'. Specify which to use with the <workspace> argument. + '{0}' içinde MSBuild proje dosyası veya çözüm dosyası bulunamadı. Hangisinin <çalışma alanı> bağımsız değişkeni ile kullanılacağını belirtin. + + + + Could not format '{0}'. Format currently supports only C# and Visual Basic projects. + '{0}' biçiminde değil. Biçimi şu anda yalnızca C# ve Visual Basic projeleri desteklemektedir. + + + + Delete {0} characters. + Delete {0} characters. + + + + Determining diagnostics... + Tanılama belirleniyor... + + + + Determining formattable files. + Biçimlendirilebilir dosyalar belirleniyor. + + + + Doesn't execute an implicit restore before formatting. + Doesn't execute an implicit restore before formatting. + + + + Failed to apply code fix {0} for {1}: {2} + {1} için {0} kod düzeltmesi uygulanamadı: {2} + + + + Failed to save formatting changes. + Biçimlendirme değişiklikleri kaydedilemedi. + + + + Fix end of line marker. + Satır sonu işaretçisini düzeltin. + + + + Fix file encoding. + Dosya kodlamasını düzeltin. + + + + Fix final newline. + Son yeni satırı düzeltin. + + + + Fix imports ordering. + İçeri aktarma sıralamasını düzeltin. + + + + Fix whitespace formatting. + Boşluk biçimlendirmesini düzeltin. + + + + Fixing diagnostics... + Tanılama düzeltiliyor... + + + + Format complete in {0}ms. + Biçimlendirme {0} ms içinde tamamlandı. + + + + Format files generated by the SDK. + Format files generated by the SDK. + + + + Formats code to match editorconfig settings. + Formats code to match editorconfig settings. + + + + Formats files without saving changes to disk. Terminates with a non-zero exit code if any files were formatted. + Değişiklikleri diske kaydetmeden dosyaları biçimlendirir. Herhangi bir dosya biçimlendirildiyse sıfır olmayan bir çıkış koduyla sonlandırılır. + + + + Formatted {0} of {1} files. + {1} dosya içinden {0} dosya biçimlendirildi. + + + + Formatting code file '{0}'. + Biçimlendirme kod dosyası '{0}'. + + + + Formatting code files in workspace '{0}'. + Çalışma alanı '{0}' kod dosyalarında biçimlendirme. + + + + Include generated code files in formatting operations. + Biçimlendirme işlemlerinde oluşturulan kod dosyalarını ekleyin. + + + + Insert '{0}'. + Insert '{0}'. + + + + Loading workspace. + Çalışma alanı yükleniyor. + + + + Log all project or solution load information to a binary log file. + Log all project or solution load information to a binary log file. + + + + Multiple MSBuild project files found in '{0}'. Specify which to use with the <workspace> argument. + '{0}' içinde birden fazla MSBuild proje dosyası bulundu. Hangisinin <çalışma alanı> bağımsız değişkeni ile kullanılacağını belirtin. + + + + Multiple MSBuild solution files found in '{0}'. Specify which to use with the <workspace> argument. + '{0}' içinde birden fazla MSBuild çözüm dosyası bulundu. Hangisinin <çalışma alanı> bağımsız değişkeni ile kullanılacağını belirtin. + + + + Project {0} is using configuration from '{1}'. + Project {0} is using configuration from '{1}'. + + + + Replace {0} characters with '{1}'. + Replace {0} characters with '{1}'. + + + + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + + + + Remove unnecessary import. + Remove unnecessary import. + + + + Run 3rd party analyzers and apply fixes. + Run 3rd party analyzers and apply fixes. + + + + Run 3rd party analyzers and apply fixes. + 3. taraf çözümleyicileri çalıştırıp düzeltmeleri uygulayın. + + + + Run code style analyzers and apply fixes. + Kod stili çözümleyicilerini çalıştırıp düzeltmeleri uygulayın. + + + + Run whitespace formatting. + Run whitespace formatting. + + + + Run whitespace formatting. Run by default when not applying fixes. + Run whitespace formatting. Run by default when not applying fixes. + + + + Running {0} analysis. + {0} analizi çalıştırılıyor. + + + + Running {0} analyzers on {1}. + Running {0} analyzers on {1}. + + + + Running formatters. + Biçimlendiriciler çalıştırılıyor. + + + + Set the verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] + Ayrıntı düzeyini ayarlayın. İzin verilen değerler şunlardır: q [uiet], [inimal] m, n [nizamî], d [ayrıntılı] ve diag [nostic] + + + + Skipping referenced project '{0}'. + Atlama projesi '{0}' başvuru. + + + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. + Üzerinde işlem yapılacak proje veya çözüm dosyası. Bir dosya belirtilmezse komut geçerli dizinde dosya arar. + + + + PROJECT | SOLUTION + PROJECT | SOLUTION + + + + Solution {0} has no projects + {0} çözümünde proje yok + + + + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + + + + The '--diagnostics' option only applies when fixing style or running analyzers. + The '--diagnostics' option only applies when fixing style or running analyzers. + + + + The dotnet CLI version is '{0}'. + dotnet CLI sürümü: '{0}'. + + + + The dotnet format version is '{0}'. + The dotnet format version is '{0}'. + + + + The dotnet runtime version is '{0}'. + The dotnet runtime version is '{0}'. + + + + The file '{0}' does not appear to be a valid project or solution file. + '{0}' dosyası geçerli proje veya çözüm dosyası gibi görünmüyor. + + + + The project file '{0}' does not exist. + Proje dosyası '{0}' yok. + + + + The severity of diagnostics to fix. Allowed values are info, warn, and error. + The severity of diagnostics to fix. Allowed values are info, warn, and error. + + + + The solution file '{0}' does not exist. + Çözüm dosyası '{0}' yok. + + + + Formatted code file '{0}'. + '{0}' kod dosyası biçimlendirildi. + + + + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + + + + Unable to fix {0}. Code fix {1} doesn't support Fix All in Solution. + {0} düzeltilemiyor. {1} kod düzeltmesi Çözümdeki Tümünü Onar seçeneğini desteklemiyor. + + + + Unable to fix {0}. Code fix {1} returned an unexpected operation. + Unable to fix {0}. Code fix {1} returned an unexpected operation. + + + + Unable to fix {0}. No associated code fix found. + Unable to fix {0}. No associated code fix found. + + + + Unable to locate MSBuild. Ensure the .NET SDK was installed with the official installer. + MSBuild bulunamıyor. .NET SDK'nın resmi yükleyici kullanılarak yüklendiğinden emin olun. + + + + Unable to locate dotnet CLI. Ensure that it is on the PATH. + dotnet CLI bulunamıyor. dotnet CLI'nin PATH üzerinde olduğundan emin olun. + + + + Unable to organize imports for '{0}'. The document is too complex. + '{0}' için içeri aktarmalar düzenlenemiyor. Belge çok karmaşık. + + + + Using MSBuild.exe located in '{0}'. + '{0}' içinde bulunan MSBuild.exe kullanılıyor. + + + + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + + + + Warnings were encountered while loading the workspace. Set the verbosity option to the 'diagnostic' level to log warnings. + Çalışma alanı yüklenirken uyarılarla karşılaşıldı. Uyarıları günlüğe kaydetmek için ayrıntı düzeyi seçeneğini 'tanılama' düzeyine ayarlayın. + + + + Whether to treat the `<workspace>` argument as a simple folder of files. + `<çalışma alanı>` bağımsız değişkeninin basit bir dosya klasörü olarak değerlendirilip değerlendirilmeyeceğini belirtir. + + + + Writing formatting report to: '{0}'. + Biçimlendirme raporu '{0}' konumuna yazılıyor. + + + + + \ No newline at end of file diff --git a/src/BuiltInTools/dotnet-format/xlf/Resources.zh-Hans.xlf b/src/BuiltInTools/dotnet-format/xlf/Resources.zh-Hans.xlf new file mode 100644 index 000000000000..28792812f6ea --- /dev/null +++ b/src/BuiltInTools/dotnet-format/xlf/Resources.zh-Hans.xlf @@ -0,0 +1,417 @@ + + + + + + A list of relative file or folder paths to exclude from formatting. + 不进行格式化的相对文件或文件夹路径的列表。 + + + + A list of relative file or folder paths to include in formatting. All files are formatted if empty. + 要进行格式化的相对文件或文件夹路径的列表。如果为空,则格式化所有文件。 + + + + A path to a solution file, a project file, or a folder containing a solution or project file. If a path is not specified then the current directory is used. + 指向解决方案文件、项目文件或包含解决方案或项目文件的文件夹的路径。如果未指定路径,则使用当前目录。 + + + + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + + + + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + + + + Accepts a file path, which if provided, will produce a json report in the given directory. + 接受文件路径(若提供)将在给定目录中生成 json 报表。 + + + + Accepts a file path which if provided will produce a json report in the given directory. + Accepts a file path which if provided will produce a json report in the given directory. + + + + Analysis complete in {0}ms. + 将在 {0} 毫秒后完成分析。 + + + + Analyzer Reference + 分析器引用 + + + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the <workspace> argument. + 在“{0}”中同时找到 MSBuild 项目文件和解决方案文件。请指定要用于 <workspace> 参数的文件。 + + + + Cannot specify the '--folder' option when fixing style. + Cannot specify the '--folder' option when fixing style. + + + + Cannot specify the '--folder' option when running analyzers. + Cannot specify the '--folder' option when running analyzers. + + + + Cannot specify the '--folder' option with '--no-restore'. + Cannot specify the '--folder' option with '--no-restore'. + + + + Cannot specify the '--folder' option when writing a binary log. + Cannot specify the '--folder' option when writing a binary log. + + + + Code Style + 代码样式 + + + + Complete in {0}ms. + 在 {0} 毫秒后完成。 + + + + Could not find a MSBuild project file or solution file in '{0}'. Specify which to use with the <workspace> argument. + 无法在“{0}”中找到 MSBuild 项目文件或解决方案文件。请指定要用于 <workspace> 参数的文件。 + + + + Could not format '{0}'. Format currently supports only C# and Visual Basic projects. + 无法设置“{0}”的格式。该格式当前仅支持 C# 和 Visual Basic 项目。 + + + + Delete {0} characters. + Delete {0} characters. + + + + Determining diagnostics... + 正在确定诊断… + + + + Determining formattable files. + 正在确定可格式化的文件。 + + + + Doesn't execute an implicit restore before formatting. + Doesn't execute an implicit restore before formatting. + + + + Failed to apply code fix {0} for {1}: {2} + 未能对 {1} 应用代码修复 {0}: {2} + + + + Failed to save formatting changes. + 未能保存格式更改。 + + + + Fix end of line marker. + 修复行尾标记。 + + + + Fix file encoding. + 修复文件编码。 + + + + Fix final newline. + 修复最后一个换行符。 + + + + Fix imports ordering. + 修复导入顺序。 + + + + Fix whitespace formatting. + 修复空格格式。 + + + + Fixing diagnostics... + 正在修复诊断… + + + + Format complete in {0}ms. + 将在 {0} 毫秒后完成格式化。 + + + + Format files generated by the SDK. + Format files generated by the SDK. + + + + Formats code to match editorconfig settings. + Formats code to match editorconfig settings. + + + + Formats files without saving changes to disk. Terminates with a non-zero exit code if any files were formatted. + 将文件格式化且不将所作更改保存到磁盘中。如果有文件进行了格式化处理,则以非零退出代码终止。 + + + + Formatted {0} of {1} files. + 已将 {0} 个文件格式化(共 {1} 个)。 + + + + Formatting code file '{0}'. + 正在设置代码文件“{0}”的格式。 + + + + Formatting code files in workspace '{0}'. + 正在设置工作区“{0}”中代码文件的格式。 + + + + Include generated code files in formatting operations. + 在格式化操作中包含所生成的代码文件。 + + + + Insert '{0}'. + Insert '{0}'. + + + + Loading workspace. + 正在加载工作区。 + + + + Log all project or solution load information to a binary log file. + Log all project or solution load information to a binary log file. + + + + Multiple MSBuild project files found in '{0}'. Specify which to use with the <workspace> argument. + 在“{0}”中找到多个 MSBuild 项目文件。请指定要用于 <workspace> 参数的文件。 + + + + Multiple MSBuild solution files found in '{0}'. Specify which to use with the <workspace> argument. + 在“{0}”中找到多个 MSBuild 解决方案文件。请指定要用于 <workspace> 参数的文件。 + + + + Project {0} is using configuration from '{1}'. + Project {0} is using configuration from '{1}'. + + + + Replace {0} characters with '{1}'. + Replace {0} characters with '{1}'. + + + + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + + + + Remove unnecessary import. + Remove unnecessary import. + + + + Run 3rd party analyzers and apply fixes. + Run 3rd party analyzers and apply fixes. + + + + Run 3rd party analyzers and apply fixes. + 运行第三方分析器并应用修补程序。 + + + + Run code style analyzers and apply fixes. + 运行代码样式分析器并应用修补程序。 + + + + Run whitespace formatting. + Run whitespace formatting. + + + + Run whitespace formatting. Run by default when not applying fixes. + Run whitespace formatting. Run by default when not applying fixes. + + + + Running {0} analysis. + 正在运行 {0} 分析。 + + + + Running {0} analyzers on {1}. + Running {0} analyzers on {1}. + + + + Running formatters. + 正在运行格式化程序。 + + + + Set the verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] + 设置详细级别。允许的值为 q[uiet]、m[inimal]、n[ormal]、d[etailed] 和 diag[nostic] + + + + Skipping referenced project '{0}'. + 正在跳过引用的项目“{0}”。 + + + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. + 要操作的项目或解决方案文件。如果没有指定文件,则命令将在当前目录里搜索一个文件。 + + + + PROJECT | SOLUTION + PROJECT | SOLUTION + + + + Solution {0} has no projects + 解决方案 {0} 不包含任何项目 + + + + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + + + + The '--diagnostics' option only applies when fixing style or running analyzers. + The '--diagnostics' option only applies when fixing style or running analyzers. + + + + The dotnet CLI version is '{0}'. + dotnet CLI 版本为“{0}”。 + + + + The dotnet format version is '{0}'. + The dotnet format version is '{0}'. + + + + The dotnet runtime version is '{0}'. + The dotnet runtime version is '{0}'. + + + + The file '{0}' does not appear to be a valid project or solution file. + 文件“{0}”似乎不是有效的项目或解决方案文件。 + + + + The project file '{0}' does not exist. + 项目文件“{0}” 不存在。 + + + + The severity of diagnostics to fix. Allowed values are info, warn, and error. + The severity of diagnostics to fix. Allowed values are info, warn, and error. + + + + The solution file '{0}' does not exist. + 解决方案文件“{0}”不存在。 + + + + Formatted code file '{0}'. + 已将代码文件“{0}”格式化。 + + + + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + + + + Unable to fix {0}. Code fix {1} doesn't support Fix All in Solution. + 无法修复 {0}。代码修补程序 {1} 不支持修复解决方案中的所有内容。 + + + + Unable to fix {0}. Code fix {1} returned an unexpected operation. + Unable to fix {0}. Code fix {1} returned an unexpected operation. + + + + Unable to fix {0}. No associated code fix found. + Unable to fix {0}. No associated code fix found. + + + + Unable to locate MSBuild. Ensure the .NET SDK was installed with the official installer. + 无法找到 MSBuild。请确保 .NET SDK 是与官方安装程序一起安装的。 + + + + Unable to locate dotnet CLI. Ensure that it is on the PATH. + 找不到 dotnet CLI。请确保它在路径上。 + + + + Unable to organize imports for '{0}'. The document is too complex. + 无法整理“{0}”的导入项。文档太复杂。 + + + + Using MSBuild.exe located in '{0}'. + 使用“{0}”中的 MSBuild.exe。 + + + + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + + + + Warnings were encountered while loading the workspace. Set the verbosity option to the 'diagnostic' level to log warnings. + 加载工作区时遇到警告。要记录警告,请将“详细级别”选项设置为“诊断”级别。 + + + + Whether to treat the `<workspace>` argument as a simple folder of files. + 是否将 "<workspace>" 参数视为文件的简单文件夹。 + + + + Writing formatting report to: '{0}'. + 正在将格式报表写入:“{0}”。 + + + + + \ No newline at end of file diff --git a/src/BuiltInTools/dotnet-format/xlf/Resources.zh-Hant.xlf b/src/BuiltInTools/dotnet-format/xlf/Resources.zh-Hant.xlf new file mode 100644 index 000000000000..78caaaf3b6bf --- /dev/null +++ b/src/BuiltInTools/dotnet-format/xlf/Resources.zh-Hant.xlf @@ -0,0 +1,417 @@ + + + + + + A list of relative file or folder paths to exclude from formatting. + 要從格式化作業中排除的檔案或資料夾相對路徑清單。 + + + + A list of relative file or folder paths to include in formatting. All files are formatted if empty. + 要包含在格式化作業中的檔案或資料夾相對路徑清單。若保留空白,則會將所有檔案格式化。 + + + + A path to a solution file, a project file, or a folder containing a solution or project file. If a path is not specified then the current directory is used. + 解決方案檔、專案檔,或包含解決方案或專案檔之資料夾的路徑。若未指定路徑,就會使用目前的目錄。 + + + + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to ignore when fixing code style or 3rd party issues. + + + + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + A space separated list of diagnostic ids to use as a filter when fixing code style or 3rd party issues. + + + + Accepts a file path, which if provided, will produce a json report in the given directory. + 接受檔案路徑 (如果有提供) 將在指定的目錄中產生 json 報告。 + + + + Accepts a file path which if provided will produce a json report in the given directory. + Accepts a file path which if provided will produce a json report in the given directory. + + + + Analysis complete in {0}ms. + 分析將於 {0} 毫秒後完成。 + + + + Analyzer Reference + 分析器參考 + + + + Both a MSBuild project file and solution file found in '{0}'. Specify which to use with the <workspace> argument. + 在 '{0}' 中同時找到 MSBuild 專案檔與解決方案檔。請指定要用於 <workspace> 引數的檔案。 + + + + Cannot specify the '--folder' option when fixing style. + Cannot specify the '--folder' option when fixing style. + + + + Cannot specify the '--folder' option when running analyzers. + Cannot specify the '--folder' option when running analyzers. + + + + Cannot specify the '--folder' option with '--no-restore'. + Cannot specify the '--folder' option with '--no-restore'. + + + + Cannot specify the '--folder' option when writing a binary log. + Cannot specify the '--folder' option when writing a binary log. + + + + Code Style + 程式碼樣式 + + + + Complete in {0}ms. + {0} 毫秒後完成。 + + + + Could not find a MSBuild project file or solution file in '{0}'. Specify which to use with the <workspace> argument. + 在 '{0}' 中找不到 MSBuild 專案檔或解決方案檔。請指定要用於 <workspace> 引數的檔案。 + + + + Could not format '{0}'. Format currently supports only C# and Visual Basic projects. + 無法將 '{0}' 格式化。格式化目前只支援 C# 和 Visual Basic 專案。 + + + + Delete {0} characters. + Delete {0} characters. + + + + Determining diagnostics... + 正在判斷診斷... + + + + Determining formattable files. + 正在判斷可格式化的檔案。 + + + + Doesn't execute an implicit restore before formatting. + Doesn't execute an implicit restore before formatting. + + + + Failed to apply code fix {0} for {1}: {2} + 無法為 {1} 套用程式碼修正 {0}: {2} + + + + Failed to save formatting changes. + 無法儲存格式化變更。 + + + + Fix end of line marker. + 修正行結尾標記。 + + + + Fix file encoding. + 修正檔案編碼。 + + + + Fix final newline. + 修正結尾換行。 + + + + Fix imports ordering. + 修正匯入排序。 + + + + Fix whitespace formatting. + 修正空白字元格式化。 + + + + Fixing diagnostics... + 正在修正診斷... + + + + Format complete in {0}ms. + 格式化作業於 {0} 毫秒後完成。 + + + + Format files generated by the SDK. + Format files generated by the SDK. + + + + Formats code to match editorconfig settings. + Formats code to match editorconfig settings. + + + + Formats files without saving changes to disk. Terminates with a non-zero exit code if any files were formatted. + 將檔案格式化,但不將變更儲存到磁碟。如果有任何檔案已格式化,則作業會終止,並產生非零的結束代碼。 + + + + Formatted {0} of {1} files. + 已將 {0}/{1} 個檔案格式化。 + + + + Formatting code file '{0}'. + 正在將程式碼檔案 '{0}' 格式化。 + + + + Formatting code files in workspace '{0}'. + 正在將工作區 '{0}' 中的程式碼檔案格式化。 + + + + Include generated code files in formatting operations. + 在格式化作業中包含產生的程式碼檔案。 + + + + Insert '{0}'. + Insert '{0}'. + + + + Loading workspace. + 正在載入工作區。 + + + + Log all project or solution load information to a binary log file. + Log all project or solution load information to a binary log file. + + + + Multiple MSBuild project files found in '{0}'. Specify which to use with the <workspace> argument. + 在 '{0}' 中找到多個 MSBuild 專案檔。請指定要用於 <workspace> 引數的檔案。 + + + + Multiple MSBuild solution files found in '{0}'. Specify which to use with the <workspace> argument. + 在 '{0}' 中找到多個 MSBuild 解決方案檔。請指定要用於 <workspace> 引數的檔案。 + + + + Project {0} is using configuration from '{1}'. + Project {0} is using configuration from '{1}'. + + + + Replace {0} characters with '{1}'. + Replace {0} characters with '{1}'. + + + + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + Required references did not load for {0} or referenced project. Run `dotnet restore` prior to formatting. + + + + Remove unnecessary import. + Remove unnecessary import. + + + + Run 3rd party analyzers and apply fixes. + Run 3rd party analyzers and apply fixes. + + + + Run 3rd party analyzers and apply fixes. + 執行協力廠商分析器並套用修正。 + + + + Run code style analyzers and apply fixes. + 執行程式碼樣式分析器並套用修正。 + + + + Run whitespace formatting. + Run whitespace formatting. + + + + Run whitespace formatting. Run by default when not applying fixes. + Run whitespace formatting. Run by default when not applying fixes. + + + + Running {0} analysis. + 正在執行 {0} 分析。 + + + + Running {0} analyzers on {1}. + Running {0} analyzers on {1}. + + + + Running formatters. + 正在執行格式器。 + + + + Set the verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] + 設定詳細資訊層級。允許的值為 q[uiet]、m[inimal]、n[ormal]、d[etailed] 和 diag[nostic] + + + + Skipping referenced project '{0}'. + 跳過參考的專案 '{0}’。 + + + + The project or solution file to operate on. If a file is not specified, the command will search the current directory for one. + 要操作的專案或解決方案。若未指定檔案,命令就會在目前的目錄中搜尋一個檔案。 + + + + PROJECT | SOLUTION + PROJECT | SOLUTION + + + + Solution {0} has no projects + 解決方案 {0} 沒有任何專案 + + + + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + Standard input markers ('/dev/stdin', '-') can only be used either with `--include` or `--exclude`, but not both. + + + + The '--diagnostics' option only applies when fixing style or running analyzers. + The '--diagnostics' option only applies when fixing style or running analyzers. + + + + The dotnet CLI version is '{0}'. + dotnet CLI 版本為 '{0}'。 + + + + The dotnet format version is '{0}'. + The dotnet format version is '{0}'. + + + + The dotnet runtime version is '{0}'. + The dotnet runtime version is '{0}'. + + + + The file '{0}' does not appear to be a valid project or solution file. + 檔案 '{0}' 似乎不是有效的專案或解決方案檔。 + + + + The project file '{0}' does not exist. + 專案檔 '{0}' 不存在。 + + + + The severity of diagnostics to fix. Allowed values are info, warn, and error. + The severity of diagnostics to fix. Allowed values are info, warn, and error. + + + + The solution file '{0}' does not exist. + 解決方案檔 '{0}' 不存在。 + + + + Formatted code file '{0}'. + 正在將程式碼檔案 '{0}' 格式化。 + + + + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + Unable to fix {0}. Code fix {1} didn't return a Fix All action. + + + + Unable to fix {0}. Code fix {1} doesn't support Fix All in Solution. + 無法修正 {0}。程式碼修正 {1} 不支援 [在解決方案中全部修正]。 + + + + Unable to fix {0}. Code fix {1} returned an unexpected operation. + Unable to fix {0}. Code fix {1} returned an unexpected operation. + + + + Unable to fix {0}. No associated code fix found. + Unable to fix {0}. No associated code fix found. + + + + Unable to locate MSBuild. Ensure the .NET SDK was installed with the official installer. + 找不到 MSBuild。請確認已使用正式安裝程式安裝了 .NET SDK。 + + + + Unable to locate dotnet CLI. Ensure that it is on the PATH. + 找不到 dotnet CLI。請確認其位於 PATH 上。 + + + + Unable to organize imports for '{0}'. The document is too complex. + 無法組織 '{0}' 的匯入。文件太複雜。 + + + + Using MSBuild.exe located in '{0}'. + 正在使用位於 '{0}' 的 MSBuild.exe。 + + + + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + Verify no formatting changes would be performed. Terminates with a non-zero exit code if any files would have been formatted. + + + + Warnings were encountered while loading the workspace. Set the verbosity option to the 'diagnostic' level to log warnings. + 載入工作區時發生警告。請將詳細程度選項設定為 [診斷] 層級以記錄警告。 + + + + Whether to treat the `<workspace>` argument as a simple folder of files. + 是否要將 `<workspace>` 引數視為檔案的簡易資料夾。 + + + + Writing formatting report to: '{0}'. + 正在將格式化報告寫入: '{0}'。 + + + + + \ No newline at end of file diff --git a/src/Layout/redist/redist.csproj b/src/Layout/redist/redist.csproj index 47f1a94ae3db..98656720694b 100644 --- a/src/Layout/redist/redist.csproj +++ b/src/Layout/redist/redist.csproj @@ -26,7 +26,6 @@ - @@ -69,6 +68,7 @@ + diff --git a/src/Layout/redist/targets/GenerateLayout.targets b/src/Layout/redist/targets/GenerateLayout.targets index f809881cc6e4..28f5878c77ee 100644 --- a/src/Layout/redist/targets/GenerateLayout.targets +++ b/src/Layout/redist/targets/GenerateLayout.targets @@ -105,20 +105,17 @@ - + $(OutputPath)/DotnetTools/dotnet-format - - - + + + - - diff --git a/test.cmd b/test.cmd index afc2145fc031..e94a81955af3 100644 --- a/test.cmd +++ b/test.cmd @@ -1,3 +1,3 @@ @echo off -powershell -NoLogo -NoProfile -ExecutionPolicy ByPass -Command "& """%~dp0eng\common\build.ps1""" -test %*" +powershell -NoLogo -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0eng\common\build.ps1""" -test -msbuildEngine dotnet %*" exit /b %ErrorLevel% diff --git a/test/Directory.Build.targets b/test/Directory.Build.targets index 689d2437dbb0..369217f9e2b1 100644 --- a/test/Directory.Build.targets +++ b/test/Directory.Build.targets @@ -14,6 +14,11 @@ $(PackageOutputPath)tests\ + + + + true + @@ -24,7 +29,6 @@ - diff --git a/test/UnitTests.proj b/test/UnitTests.proj index 2cc23bc3d9ac..8190414b29d0 100644 --- a/test/UnitTests.proj +++ b/test/UnitTests.proj @@ -76,7 +76,7 @@ - + diff --git a/test/dotnet-format.Tests/tests/.editorconfig b/test/dotnet-format.Tests/tests/.editorconfig new file mode 100644 index 000000000000..6f3d1d667a42 --- /dev/null +++ b/test/dotnet-format.Tests/tests/.editorconfig @@ -0,0 +1,4 @@ +# Code files +[*.{cs,csx,vb,vbx}] +# CA1822: Make member static +dotnet_diagnostic.CA1822.severity = silent \ No newline at end of file diff --git a/test/dotnet-format.Tests/tests/Analyzers/AnalyzerAssemblyGenerator.cs b/test/dotnet-format.Tests/tests/Analyzers/AnalyzerAssemblyGenerator.cs new file mode 100644 index 000000000000..f7f3d3ce3d2e --- /dev/null +++ b/test/dotnet-format.Tests/tests/Analyzers/AnalyzerAssemblyGenerator.cs @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Composition; +using System.Reflection; + +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Analyzers +{ + public static class AnalyzerAssemblyGenerator + { + private static IEnumerable s_references; + + private static async Task> GetReferencesAsync() + { + if (s_references is not null) + { + return s_references; + } + + var references = new List() + { + MetadataReference.CreateFromFile(typeof(ImmutableArray).Assembly.Location), + MetadataReference.CreateFromFile(typeof(SharedAttribute).Assembly.Location), + MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location), + MetadataReference.CreateFromFile(typeof(DiagnosticAnalyzer).Assembly.Location), + MetadataReference.CreateFromFile(typeof(CodeFixProvider).Assembly.Location), + }; + + // Resolve the targeting pack and the target framework used to compile the test assembly. + var netCurrentTargetingPackVersion = (string)AppContext.GetData("ReferenceAssemblies.NetCurrent.TargetingPackVersion")!; + var netCurrentTargetFramework = (string)AppContext.GetData("ReferenceAssemblies.NetCurrent.TargetFramework")!; + var nugetConfigPath = Path.Combine(TestContext.Current.TestExecutionDirectory, "NuGet.config"); + ReferenceAssemblies netCurrentReferenceAssemblies = new(netCurrentTargetFramework, + new PackageIdentity("Microsoft.NETCore.App.Ref", netCurrentTargetingPackVersion), + Path.Combine("ref", netCurrentTargetFramework)); + netCurrentReferenceAssemblies = netCurrentReferenceAssemblies.WithNuGetConfigFilePath(nugetConfigPath); + + var netcoreMetadataReferences = await netCurrentReferenceAssemblies.ResolveAsync(LanguageNames.CSharp, CancellationToken.None); + references.AddRange(netcoreMetadataReferences.Where(reference => Path.GetFileName(reference.Display) != "System.Collections.Immutable.dll")); + + s_references = references; + return references; + } + + public static SyntaxTree GenerateCodeFix(string typeName, string diagnosticId) + { + var codefix = $@" +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof({typeName})), Shared] +public class {typeName} : CodeFixProvider +{{ + public const string DiagnosticId = ""{diagnosticId}""; + + public sealed override ImmutableArray FixableDiagnosticIds + => ImmutableArray.Create(DiagnosticId); + + public sealed override FixAllProvider GetFixAllProvider() + {{ + return WellKnownFixAllProviders.BatchFixer; + }} + + public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) + {{ + throw new NotImplementedException(); + }} +}}"; + return CSharpSyntaxTree.ParseText(codefix); + } + + public static SyntaxTree GenerateAnalyzerCode(string typeName, string diagnosticId) + { + var analyzer = $@" +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class {typeName} : DiagnosticAnalyzer +{{ + public const string DiagnosticId = ""{diagnosticId}""; + internal static readonly LocalizableString Title = ""{typeName} Title""; + internal static readonly LocalizableString MessageFormat = ""{typeName} '{{0}}'""; + internal const string Category = ""{typeName} Category""; + internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, true); + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + public override void Initialize(AnalysisContext context) + {{ + }} +}}"; + return CSharpSyntaxTree.ParseText(analyzer); + } + + public static async Task GenerateAssemblyAsync(params SyntaxTree[] trees) + { + var assemblyName = Guid.NewGuid().ToString(); + var references = await GetReferencesAsync(); + var compilation = CSharpCompilation.Create(assemblyName, trees, references, + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + + using var ms = new MemoryStream(); + var result = compilation.Emit(ms); + if (!result.Success) + { + var failures = result.Diagnostics.Where(diagnostic => + diagnostic.IsWarningAsError || + diagnostic.Severity == DiagnosticSeverity.Error) + .Select(diagnostic => $"{diagnostic.Id}: {diagnostic.GetMessage()}"); + + throw new Exception(string.Join(Environment.NewLine, failures)); + } + else + { + ms.Seek(0, SeekOrigin.Begin); + var assembly = Assembly.Load(ms.ToArray()); + return assembly; + } + } + } +} diff --git a/test/dotnet-format.Tests/tests/Analyzers/CodeStyleAnalyzerFormatterTests.cs b/test/dotnet-format.Tests/tests/Analyzers/CodeStyleAnalyzerFormatterTests.cs new file mode 100644 index 000000000000..dbdfab09fbcf --- /dev/null +++ b/test/dotnet-format.Tests/tests/Analyzers/CodeStyleAnalyzerFormatterTests.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Tools.Analyzers; +using Microsoft.CodeAnalysis.Tools.Formatters; +using Microsoft.CodeAnalysis.Tools.Tests.Formatters; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Analyzers +{ + public class CodeStyleAnalyzerFormatterTests : CSharpFormatterTests + { + private protected override ICodeFormatter Formatter => AnalyzerFormatter.CodeStyleFormatter; + + public CodeStyleAnalyzerFormatterTests(ITestOutputHelper output) + { + TestOutputHelper = output; + } + + [Fact] + public async Task TestUseVarCodeStyle_AppliesWhenNotUsingVar() + { + var testCode = @" +using System.Collections.Generic; + +class C +{ + void M() + { + object obj = new object(); + List list = new List(); + int count = 5; + } +}"; + + var expectedCode = @" +using System.Collections.Generic; + +class C +{ + void M() + { + var obj = new object(); + var list = new List(); + var count = 5; + } +}"; + + var editorConfig = new Dictionary() + { + /// Prefer "var" everywhere + ["dotnet_diagnostic.IDE0007.severity"] = "error", + ["csharp_style_var_for_built_in_types"] = "true:error", + ["csharp_style_var_when_type_is_apparent"] = "true:error", + ["csharp_style_var_elsewhere"] = "true:error", + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig, fixCategory: FixCategory.CodeStyle); + } + + [Fact] + public async Task TestNonFixableCompilerDiagnostics_AreNotReported() + { + var testCode = @" +class C +{ + public int M() + { + return null; // Cannot convert null to 'int' because it is a non-nullable value type (CS0037) + } +}"; + + await AssertNoReportedFileChangesAsync(testCode, "root = true", fixCategory: FixCategory.CodeStyle, codeStyleSeverity: DiagnosticSeverity.Warning); + } + } +} diff --git a/test/dotnet-format.Tests/tests/Analyzers/FilterDiagnosticsTests.cs b/test/dotnet-format.Tests/tests/Analyzers/FilterDiagnosticsTests.cs new file mode 100644 index 000000000000..f6920a8c3cc1 --- /dev/null +++ b/test/dotnet-format.Tests/tests/Analyzers/FilterDiagnosticsTests.cs @@ -0,0 +1,208 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Reflection; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Tools.Analyzers; +using Microsoft.CodeAnalysis.Tools.Formatters; +using Microsoft.CodeAnalysis.Tools.Tests.Formatters; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Analyzers +{ + using static AnalyzerAssemblyGenerator; + + public class FilterDiagnosticsTests : CSharpFormatterTests + { + [Fact] + public async Task TestFilterWarning() + { + var (_, solution) = await GetSolutionAsync(); + var projectAnalyzersAndFixers = await GetProjectAnalyzersAndFixersAsync(solution); + var project = solution.Projects.First(); + var formattablePaths = ImmutableHashSet.Create(project.Documents.First().FilePath); + var minimumSeverity = DiagnosticSeverity.Warning; + var diagnostics = ImmutableHashSet.Empty; + var excludeDiagnostics = ImmutableHashSet.Empty; + var result = await AnalyzerFormatter.FilterAnalyzersAsync( + solution, + projectAnalyzersAndFixers, + formattablePaths, + minimumSeverity, + diagnostics, + excludeDiagnostics, + CancellationToken.None); + var (_, analyzers) = Assert.Single(result); + Assert.Single(analyzers); + } + + [Fact] + public async Task TestFilterError() + { + var (_, solution) = await GetSolutionAsync(); + var projectAnalyzersAndFixers = await GetProjectAnalyzersAndFixersAsync(solution); + var project = solution.Projects.First(); + var formattablePaths = ImmutableHashSet.Create(project.Documents.First().FilePath); + var minimumSeverity = DiagnosticSeverity.Error; + var diagnostics = ImmutableHashSet.Empty; + var excludeDiagnostics = ImmutableHashSet.Empty; + var result = await AnalyzerFormatter.FilterAnalyzersAsync( + solution, + projectAnalyzersAndFixers, + formattablePaths, + minimumSeverity, + diagnostics, + excludeDiagnostics, + CancellationToken.None); + var (_, analyzers) = Assert.Single(result); + Assert.Empty(analyzers); + } + + [Fact] + public async Task TestFilterDiagnostics_NotInDiagnosticsList() + { + var (_, solution) = await GetSolutionAsync(); + var projectAnalyzersAndFixers = await GetProjectAnalyzersAndFixersAsync(solution); + var project = solution.Projects.First(); + var formattablePaths = ImmutableHashSet.Create(project.Documents.First().FilePath); + var minimumSeverity = DiagnosticSeverity.Warning; + var diagnostics = ImmutableHashSet.Create("IDE0005"); + var excludeDiagnostics = ImmutableHashSet.Empty; + var result = await AnalyzerFormatter.FilterAnalyzersAsync( + solution, + projectAnalyzersAndFixers, + formattablePaths, + minimumSeverity, + diagnostics, + excludeDiagnostics, + CancellationToken.None); + var (_, analyzers) = Assert.Single(result); + Assert.Empty(analyzers); + } + + [Fact] + public async Task TestFilterDiagnostics_InDiagnosticsList() + { + var (_, solution) = await GetSolutionAsync(); + var projectAnalyzersAndFixers = await GetProjectAnalyzersAndFixersAsync(solution); + var project = solution.Projects.First(); + var formattablePaths = ImmutableHashSet.Create(project.Documents.First().FilePath); + var minimumSeverity = DiagnosticSeverity.Warning; + var diagnostics = ImmutableHashSet.Create("DiagnosticAnalyzerId"); + var excludeDiagnostics = ImmutableHashSet.Empty; + var result = await AnalyzerFormatter.FilterAnalyzersAsync( + solution, + projectAnalyzersAndFixers, + formattablePaths, + minimumSeverity, + diagnostics, + excludeDiagnostics, + CancellationToken.None); + var (_, analyzers) = Assert.Single(result); + Assert.Single(analyzers); + } + + [Fact] + public async Task TestFilterDiagnostics_ExcludedFromDiagnosticsList() + { + var (_, solution) = await GetSolutionAsync(); + var projectAnalyzersAndFixers = await GetProjectAnalyzersAndFixersAsync(solution); + var project = solution.Projects.First(); + var formattablePaths = ImmutableHashSet.Create(project.Documents.First().FilePath); + var minimumSeverity = DiagnosticSeverity.Warning; + var diagnostics = ImmutableHashSet.Empty; + var excludeDiagnostics = ImmutableHashSet.Create("DiagnosticAnalyzerId"); + var result = await AnalyzerFormatter.FilterAnalyzersAsync( + solution, + projectAnalyzersAndFixers, + formattablePaths, + minimumSeverity, + diagnostics, + excludeDiagnostics, + CancellationToken.None); + var (_, analyzers) = Assert.Single(result); + Assert.Empty(analyzers); + } + + [Fact] + public async Task TestFilterDiagnostics_ExcludeTrumpsInclude() + { + var (_, solution) = await GetSolutionAsync(); + var projectAnalyzersAndFixers = await GetProjectAnalyzersAndFixersAsync(solution); + var project = solution.Projects.First(); + var formattablePaths = ImmutableHashSet.Create(project.Documents.First().FilePath); + var minimumSeverity = DiagnosticSeverity.Warning; + var diagnostics = ImmutableHashSet.Create("DiagnosticAnalyzerId"); + var excludeDiagnostics = ImmutableHashSet.Create("DiagnosticAnalyzerId"); + var result = await AnalyzerFormatter.FilterAnalyzersAsync( + solution, + projectAnalyzersAndFixers, + formattablePaths, + minimumSeverity, + diagnostics, + excludeDiagnostics, + CancellationToken.None); + var (_, analyzers) = Assert.Single(result); + Assert.Empty(analyzers); + } + + private static async Task GetAnalyzersAndFixersAsync(string language) + { + var assemblies = new[] + { + await GenerateAssemblyAsync( + GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId"), + GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId")) + }; + + var analyzers = assemblies + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => typeof(DiagnosticAnalyzer).IsAssignableFrom(type)) + .Where(type => type.GetCustomAttribute(inherit: false) is { } attribute && attribute.Languages.Contains(language)) + .Select(type => (DiagnosticAnalyzer)Activator.CreateInstance(type)) + .OfType() + .ToImmutableArray(); + + var codeFixes = AnalyzerFinderHelpers.LoadFixers(assemblies, language); + return new AnalyzersAndFixers(analyzers, codeFixes); + } + + private Task<(Workspace workspace, Solution solution)> GetSolutionAsync() + { + var text = SourceText.From(""); + TestState.Sources.Add(text); + + var editorConfig = $@" +root = true +[*.cs] +dotnet_diagnostic.DiagnosticAnalyzerId.severity = warning +"; + + return GetSolutionAsync( + TestState.Sources.ToArray(), + TestState.AdditionalFiles.ToArray(), + TestState.AdditionalReferences.ToArray(), + editorConfig); + } + + private async Task> GetProjectAnalyzersAndFixersAsync(Solution solution) + { + var analyzersByLanguage = new Dictionary(); + var builder = ImmutableDictionary.CreateBuilder(); + foreach (var project in solution.Projects) + { + if (!analyzersByLanguage.TryGetValue(project.Language, out var analyzersAndFixers)) + { + analyzersAndFixers = await GetAnalyzersAndFixersAsync(project.Language); + analyzersByLanguage.Add(project.Language, analyzersAndFixers); + } + + builder.Add(project.Id, analyzersAndFixers); + } + + return builder.ToImmutable(); + } + + private protected override ICodeFormatter Formatter { get; } + } +} diff --git a/test/dotnet-format.Tests/tests/Analyzers/LoadAnalyzersAndFixersTests.cs b/test/dotnet-format.Tests/tests/Analyzers/LoadAnalyzersAndFixersTests.cs new file mode 100644 index 000000000000..464fccb47abe --- /dev/null +++ b/test/dotnet-format.Tests/tests/Analyzers/LoadAnalyzersAndFixersTests.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Reflection; + +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Tools.Analyzers; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Analyzers +{ + using static AnalyzerAssemblyGenerator; + + public class LoadAnalyzersAndFixersTests + { + private static AnalyzersAndFixers GetAnalyzersAndFixers(IEnumerable assemblies, string language) + { + var analyzers = assemblies + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => typeof(DiagnosticAnalyzer).IsAssignableFrom(type)) + .Where(type => type.GetCustomAttribute(inherit: false) is { } attribute && attribute.Languages.Contains(language)) + .Select(type => (DiagnosticAnalyzer)Activator.CreateInstance(type)) + .OfType() + .ToImmutableArray(); + + var codeFixes = AnalyzerFinderHelpers.LoadFixers(assemblies, language); + return new AnalyzersAndFixers(analyzers, codeFixes); + } + + [Fact] + public static async Task TestSingleAnalyzerAndFixerAsync() + { + var assemblies = new[] + { + await GenerateAssemblyAsync( + GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId"), + GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId")) + }; + + var (analyzers, fixers) = GetAnalyzersAndFixers(assemblies, LanguageNames.CSharp); + var analyzer = Assert.Single(analyzers); + var fixer = Assert.Single(fixers); + var analyzerDiagnosticDescriptor = Assert.Single(analyzer.SupportedDiagnostics); + var fixerDiagnosticId = Assert.Single(fixer.FixableDiagnosticIds); + Assert.Equal(analyzerDiagnosticDescriptor.Id, fixerDiagnosticId); + } + + [Fact] + public static async Task TestMultipleAnalyzersAndFixersAsync() + { + var assemblies = new[] + { + await GenerateAssemblyAsync( + GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId1"), + GenerateAnalyzerCode("DiagnosticAnalyzer2", "DiagnosticAnalyzerId2"), + GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId1"), + GenerateCodeFix("CodeFixProvider2", "DiagnosticAnalyzerId2")) + }; + + var (analyzers, fixers) = GetAnalyzersAndFixers(assemblies, LanguageNames.CSharp); + Assert.Equal(2, analyzers.Length); + Assert.Equal(2, fixers.Length); + } + + [Fact] + public static async Task TestMultipleAnalyzersAndFixersFromTwoAssembliesAsync() + { + var assemblies = new[] + { + await GenerateAssemblyAsync( + GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId1"), + GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId1")), + await GenerateAssemblyAsync( + GenerateAnalyzerCode("DiagnosticAnalyzer2", "DiagnosticAnalyzerId2"), + GenerateCodeFix("CodeFixProvider2", "DiagnosticAnalyzerId2")), + }; + var (analyzers, fixers) = GetAnalyzersAndFixers(assemblies, LanguageNames.CSharp); + Assert.Equal(2, analyzers.Length); + Assert.Equal(2, fixers.Length); + } + } +} diff --git a/test/dotnet-format.Tests/tests/Analyzers/ThirdPartyAnalyzerFormatterTests.cs b/test/dotnet-format.Tests/tests/Analyzers/ThirdPartyAnalyzerFormatterTests.cs new file mode 100644 index 000000000000..1094c6a662ad --- /dev/null +++ b/test/dotnet-format.Tests/tests/Analyzers/ThirdPartyAnalyzerFormatterTests.cs @@ -0,0 +1,211 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Tools.Analyzers; +using Microsoft.CodeAnalysis.Tools.Formatters; +using Microsoft.CodeAnalysis.Tools.Tests.Formatters; +using Microsoft.CodeAnalysis.Tools.Tests.Utilities; +using Microsoft.CodeAnalysis.Tools.Tests.XUnit; +using Microsoft.CodeAnalysis.Tools.Workspaces; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Analyzers +{ + public class ThirdPartyAnalyzerFormatterTests : CSharpFormatterTests, IAsyncLifetime + { + private static readonly string s_analyzerProjectFilePath = Path.Combine("for_analyzer_formatter", "analyzer_project", "analyzer_project.csproj"); + + private protected override ICodeFormatter Formatter => AnalyzerFormatter.ThirdPartyFormatter; + + private Project _analyzerReferencesProject; + + public ThirdPartyAnalyzerFormatterTests(ITestOutputHelper output) + { + TestOutputHelper = output; + } + + public async Task InitializeAsync() + { + var logger = new TestLogger(); + + try + { + // Restore the Analyzer packages that have been added to `for_analyzer_formatter/analyzer_project/analyzer_project.csproj` + var exitCode = await DotNetHelper.PerformRestoreAsync(s_analyzerProjectFilePath, TestOutputHelper); + Assert.Equal(0, exitCode); + + // Load the analyzer_project into a MSBuildWorkspace. + var workspacePath = Path.Combine(TestProjectsPathHelper.GetProjectsDirectory(), s_analyzerProjectFilePath); + + MSBuildRegistrar.RegisterInstance(); + var analyzerWorkspace = await MSBuildWorkspaceLoader.LoadAsync(workspacePath, WorkspaceType.Project, binaryLogPath: null, logWorkspaceWarnings: true, logger, CancellationToken.None); + + TestOutputHelper.WriteLine(logger.GetLog()); + + // From this project we can get valid AnalyzerReferences to add to our test project. + _analyzerReferencesProject = analyzerWorkspace.CurrentSolution.Projects.Single(); + } + catch + { + TestOutputHelper.WriteLine(logger.GetLog()); + throw; + } + } + + public Task DisposeAsync() + { + _analyzerReferencesProject = null; + + return Task.CompletedTask; + } + + private IEnumerable GetAnalyzerReferences(string prefix) + => _analyzerReferencesProject.AnalyzerReferences.Where(reference => reference.Display.StartsWith(prefix)); + + [MSBuildFact] + public async Task TestStyleCopBlankLineFixer_RemovesUnnecessaryBlankLines() + { + var analyzerReferences = GetAnalyzerReferences("StyleCop"); + + var testCode = @" +class C +{ + + void M() + + { + + object obj = new object(); + + + int count = 5; + + } + +} +"; + + var expectedCode = @" +class C +{ + void M() + { + object obj = new object(); + + int count = 5; + } +} +"; + + var editorConfig = new Dictionary() + { + // Turn off all diagnostics analyzers + ["dotnet_analyzer_diagnostic.severity"] = "none", + + // Two or more consecutive blank lines: Remove down to one blank line. SA1507 + ["dotnet_diagnostic.SA1507.severity"] = "error", + + // Blank line immediately before or after a { line: remove it. SA1505, SA1509 + ["dotnet_diagnostic.SA1505.severity"] = "error", + ["dotnet_diagnostic.SA1509.severity"] = "error", + + // Blank line immediately before a } line: remove it. SA1508 + ["dotnet_diagnostic.SA1508.severity"] = "error", + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig, fixCategory: FixCategory.Analyzers, analyzerReferences: analyzerReferences); + } + + [MSBuildFact] + public async Task TestIDisposableAnalyzer_AddsUsing() + { + var analyzerReferences = GetAnalyzerReferences("IDisposable"); + + var testCode = @" +using System.IO; + +class C +{ + void M() + { + var stream = File.OpenRead(string.Empty); + var b = stream.ReadByte(); + stream.Dispose(); + } +} +"; + + var expectedCode = @" +using System.IO; + +class C +{ + void M() + { + using (var stream = File.OpenRead(string.Empty)) + { + var b = stream.ReadByte(); + } + } +} +"; + + var editorConfig = new Dictionary() + { + // Turn off all diagnostics analyzers + ["dotnet_analyzer_diagnostic.severity"] = "none", + + // Prefer using. IDISP017 + ["dotnet_diagnostic.IDISP017.severity"] = "error", + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig, fixCategory: FixCategory.Analyzers, analyzerReferences: analyzerReferences); + } + + [MSBuildFact] + public async Task TestLoadingAllAnalyzers_LoadsDependenciesFromAllSearchPaths() + { + // Loads all analyzer references. + var analyzerReferences = _analyzerReferencesProject.AnalyzerReferences; + + var testCode = @" +using System.IO; + +class C +{ + void M() + { + var stream = File.OpenRead(string.Empty); + var b = stream.ReadByte(); + stream.Dispose(); + } +} +"; + + var expectedCode = @" +using System.IO; + +class C +{ + void M() + { + using (var stream = File.OpenRead(string.Empty)) + { + var b = stream.ReadByte(); + } + } +} +"; + + var editorConfig = new Dictionary() + { + // Turn off all diagnostics analyzers + ["dotnet_analyzer_diagnostic.severity"] = "none", + + // Prefer using. IDISP017 + ["dotnet_diagnostic.IDISP017.severity"] = "error", + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig, fixCategory: FixCategory.Analyzers, analyzerReferences: analyzerReferences); + } + } +} diff --git a/test/dotnet-format.Tests/tests/CodeFormatterTests.cs b/test/dotnet-format.Tests/tests/CodeFormatterTests.cs new file mode 100644 index 000000000000..3334039685a1 --- /dev/null +++ b/test/dotnet-format.Tests/tests/CodeFormatterTests.cs @@ -0,0 +1,751 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis.Tools.Tests.Utilities; +using Microsoft.CodeAnalysis.Tools.Tests.XUnit; +using Microsoft.CodeAnalysis.Tools.Utilities; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Tests +{ + public class CodeFormatterTests + { + private static readonly string s_formattedProjectPath = Path.Combine("for_code_formatter", "formatted_project"); + private static readonly string s_formattedProjectFilePath = Path.Combine(s_formattedProjectPath, "formatted_project.csproj"); + private static readonly string s_formattedSolutionFilePath = Path.Combine("for_code_formatter", "formatted_solution", "formatted_solution.sln"); + + private static readonly string s_unformattedProjectPath = Path.Combine("for_code_formatter", "unformatted_project"); + private static readonly string s_unformattedProjectFilePath = Path.Combine(s_unformattedProjectPath, "unformatted_project.csproj"); + private static readonly string s_unformattedProgramFilePath = Path.Combine(s_unformattedProjectPath, "program.cs"); + private static readonly string s_unformattedSolutionFilePath = Path.Combine("for_code_formatter", "unformatted_solution", "unformatted_solution.sln"); + + private static readonly string s_fSharpProjectPath = Path.Combine("for_code_formatter", "fsharp_project"); + private static readonly string s_fSharpProjectFilePath = Path.Combine(s_fSharpProjectPath, "fsharp_project.fsproj"); + + private static readonly string s_generatedProjectPath = Path.Combine("for_code_formatter", "generated_project"); + private static readonly string s_generatedProjectFilePath = Path.Combine(s_generatedProjectPath, "generated_project.csproj"); + + private static readonly string s_codeStyleSolutionPath = Path.Combine("for_code_formatter", "codestyle_solution"); + private static readonly string s_codeStyleSolutionFilePath = Path.Combine(s_codeStyleSolutionPath, "codestyle_solution.sln"); + + private static readonly string s_codeStyleSolutionFilterFilePath = Path.Combine(s_codeStyleSolutionPath, "codestyle_solution_filter.slnf"); + + private static readonly string s_analyzersSolutionPath = Path.Combine("for_code_formatter", "analyzers_solution"); + private static readonly string s_analyzersSolutionFilePath = Path.Combine(s_analyzersSolutionPath, "analyzers_solution.sln"); + + private static readonly string s_generatorSolutionPath = Path.Combine("for_code_formatter", "generator_solution"); + private static readonly string s_generatorSolutionFileName = "generator_solution.sln"; + + private static string[] EmptyFilesList => Array.Empty(); + + private Regex FindFormattingLogLine => new Regex(@"((.*)\(\d+,\d+\): (.*))\r|((.*)\(\d+,\d+\): (.*))"); + + private readonly ITestOutputHelper _output; + + public CodeFormatterTests(ITestOutputHelper output) + { + _output = output; + } + + [MSBuildFact] + public async Task NoFilesFormattedInFormattedProject() + { + await TestFormatWorkspaceAsync( + s_formattedProjectFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 0, + expectedFileCount: 3); + } + + [MSBuildFact] + public async Task NoFilesFormattedInFormattedSolution() + { + await TestFormatWorkspaceAsync( + s_formattedSolutionFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 0, + expectedFileCount: 3); + } + + [MSBuildFact] + public async Task FilesFormattedInUnformattedProject() + { + await TestFormatWorkspaceAsync( + s_unformattedProjectFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 2, + expectedFileCount: 6); + } + + [MSBuildFact] + public async Task NoFilesFormattedInUnformattedProjectWhenFixingCodeStyle() + { + await TestFormatWorkspaceAsync( + s_unformattedProjectFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + fixCategory: FixCategory.CodeStyle, + codeStyleSeverity: DiagnosticSeverity.Error, + expectedExitCode: 0, + expectedFilesFormatted: 0, + expectedFileCount: 6); + } + + [MSBuildFact] + public async Task GeneratedFilesFormattedInUnformattedProject() + { + var log = await TestFormatWorkspaceAsync( + s_unformattedProjectFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: true, + expectedExitCode: 0, + expectedFilesFormatted: 5, + expectedFileCount: 6); + + var logLines = log.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); + Assert.Contains(logLines, line => line.Contains("unformatted_project.AssemblyInfo.cs")); + Assert.Contains(logLines, line => line.Contains("NETCoreApp,Version=v3.1.AssemblyAttributes.cs")); + } + + [MSBuildFact] + public async Task FilesFormattedInUnformattedSolution() + { + await TestFormatWorkspaceAsync( + s_unformattedSolutionFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 2, + expectedFileCount: 6); + } + + [MSBuildFact] + public async Task FilesFormattedInUnformattedProjectFolder() + { + // Since the code files are beneath the project folder, files are found and formatted. + await TestFormatWorkspaceAsync( + Path.GetDirectoryName(s_unformattedProjectFilePath), + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 2, + expectedFileCount: 6); + } + + [MSBuildFact] + public async Task NoFilesFormattedInUnformattedSolutionFolder() + { + // Since the code files are outside the solution folder, no files are found or formatted. + await TestFormatWorkspaceAsync( + Path.GetDirectoryName(s_unformattedSolutionFilePath), + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 0, + expectedFileCount: 0); + } + + [MSBuildFact] + public async Task FSharpProjectsDoNotCreateException() + { + var log = await TestFormatWorkspaceAsync( + s_fSharpProjectFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 1, + expectedFilesFormatted: 0, + expectedFileCount: 0); + + var pattern = string.Format(Resources.Could_not_format_0_Format_currently_supports_only_CSharp_and_Visual_Basic_projects, "(.*)"); + var match = new Regex(pattern, RegexOptions.Multiline).Match(log); + + Assert.True(match.Success, log); + Assert.EndsWith(s_fSharpProjectFilePath, match.Groups[1].Value); + } + + [MSBuildFact] + public async Task OnlyFormatPathsFromList() + { + // To match a folder pattern it needs to end with a directory separator. + var include = new[] { s_unformattedProjectPath + Path.DirectorySeparatorChar }; + + await TestFormatWorkspaceAsync( + s_unformattedProjectFilePath, + include, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 2, + expectedFileCount: 6); + } + + [MSBuildFact] + public async Task OnlyFormatFilesFromList() + { + var include = new[] { s_unformattedProgramFilePath }; + + await TestFormatWorkspaceAsync( + s_unformattedProjectFilePath, + include, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 1, + expectedFileCount: 6); + } + + [MSBuildFact] + public async Task NoFilesFormattedWhenNotInList() + { + var include = new[] { Path.Combine(s_unformattedProjectPath, "does_not_exist.cs") }; + + await TestFormatWorkspaceAsync( + s_unformattedProjectFilePath, + include, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 0, + expectedFileCount: 6); + } + + [MSBuildFact] + public async Task OnlyLogFormattedFiles() + { + var include = new[] { s_unformattedProgramFilePath }; + + var log = await TestFormatWorkspaceAsync( + s_unformattedSolutionFilePath, + include, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 1, + expectedFileCount: 6); + + var pattern = string.Format(Resources.Formatted_code_file_0, @"(.*)"); + var match = new Regex(pattern, RegexOptions.Multiline).Match(log); + + Assert.True(match.Success, log); + Assert.EndsWith("Program.cs", match.Groups[1].Value); + } + + [MSBuildFact] + public async Task FormatLocationsLoggedInUnformattedProject() + { + var log = await TestFormatWorkspaceAsync( + s_unformattedProjectFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 2, + expectedFileCount: 6); + + var formatLocations = log.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries) + .Where(line => FindFormattingLogLine.Match(line).Success) + .ToArray(); + + var expectedFormatLocations = new[] + { + @"other_items\OtherClass.cs(5,3): Fix whitespace formatting.", + @"other_items\OtherClass.cs(6,3): Fix whitespace formatting.", + @"other_items\OtherClass.cs(7,5): Fix whitespace formatting.", + @"other_items\OtherClass.cs(8,5): Fix whitespace formatting.", + @"other_items\OtherClass.cs(9,7): Fix whitespace formatting.", + @"other_items\OtherClass.cs(10,5): Fix whitespace formatting.", + @"other_items\OtherClass.cs(11,3): Fix whitespace formatting.", + @"Program.cs(5,3): Fix whitespace formatting.", + @"Program.cs(6,3): Fix whitespace formatting.", + @"Program.cs(7,5): Fix whitespace formatting.", + @"Program.cs(8,5): Fix whitespace formatting.", + @"Program.cs(9,7): Fix whitespace formatting.", + @"Program.cs(10,5): Fix whitespace formatting.", + @"Program.cs(11,3): Fix whitespace formatting.", + @"other_items\OtherClass.cs(12,2): Add final newline.", + @"Program.cs(12,2): Add final newline.", + }.Select(path => path.Replace('\\', Path.DirectorySeparatorChar)).ToArray(); + + // We can't assert the location of the format message because different platform + // line endings change the position in the file. + Assert.Equal(expectedFormatLocations.Length, formatLocations.Length); + for (var index = 0; index < expectedFormatLocations.Length; index++) + { + var expectedParts = FindFormattingLogLine.Match(expectedFormatLocations[index]); + var formatParts = FindFormattingLogLine.Match(formatLocations[index]); + + // Match filename + Assert.Equal(expectedParts.Groups[2].Value, formatParts.Groups[2].Value); + // Match formatter message + Assert.Equal(expectedParts.Groups[3].Value, formatParts.Groups[3].Value); + } + } + + [MSBuildFact] + public async Task FormatLocationsNotLoggedInFormattedProject() + { + var log = await TestFormatWorkspaceAsync( + s_formattedProjectFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 0, + expectedFileCount: 3); + + var formatLocations = log.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries) + .Where(line => FindFormattingLogLine.Match(line).Success); + + Assert.Empty(formatLocations); + } + + [MSBuildFact] + public async Task LogFilesThatDontMatchExclude() + { + var include = new[] { s_unformattedProgramFilePath }; + + var log = await TestFormatWorkspaceAsync( + s_unformattedSolutionFilePath, + include, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 1, + expectedFileCount: 6); + + var pattern = string.Format(Resources.Formatted_code_file_0, @"(.*)"); + var match = new Regex(pattern, RegexOptions.Multiline).Match(log); + + Assert.True(match.Success, log); + Assert.EndsWith("Program.cs", match.Groups[1].Value); + } + + [MSBuildFact] + public async Task IgnoreFileWhenListedInExcludeList() + { + var include = new[] { s_unformattedProgramFilePath }; + + await TestFormatWorkspaceAsync( + s_unformattedSolutionFilePath, + include: include, + exclude: include, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 0, + expectedFileCount: 6); + } + + [MSBuildFact] + public async Task IgnoreFileWhenContainingFolderListedInExcludeList() + { + var include = new[] { s_unformattedProgramFilePath }; + var exclude = new[] { s_unformattedProjectPath }; + + await TestFormatWorkspaceAsync( + s_unformattedSolutionFilePath, + include: include, + exclude: exclude, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 0, + expectedFileCount: 6); + } + + [MSBuildFact] + public async Task IgnoreAllFileWhenExcludingAllFiles() + { + var include = new[] { s_unformattedProgramFilePath }; + var exclude = new[] { "**/*.*" }; + + await TestFormatWorkspaceAsync( + s_unformattedSolutionFilePath, + include: include, + exclude: exclude, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 0, + expectedFileCount: 6); + } + + [MSBuildFact] + public async Task NoFilesFormattedInGeneratedProject_WhenNotIncludingGeneratedCode() + { + await TestFormatWorkspaceAsync( + s_generatedProjectFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 0, + expectedFileCount: 3); + } + + [MSBuildFact] + public async Task FilesFormattedInGeneratedProject_WhenIncludingGeneratedCode() + { + await TestFormatWorkspaceAsync( + s_generatedProjectFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: true, + expectedExitCode: 0, + expectedFilesFormatted: 3, + expectedFileCount: 3); + } + + [MSBuildFact] + public async Task NoFilesFormattedInCodeStyleSolution_WhenNotFixingCodeStyle() + { + await TestFormatWorkspaceAsync( + s_codeStyleSolutionFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 0, + expectedFileCount: 6, + fixCategory: FixCategory.Whitespace | FixCategory.CodeStyle); + } + + [MSBuildFact] + public async Task NoFilesFormattedInCodeStyleSolution_WhenFixingCodeStyleErrors() + { + await TestFormatWorkspaceAsync( + s_codeStyleSolutionFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 0, + expectedFileCount: 6, + fixCategory: FixCategory.Whitespace | FixCategory.CodeStyle, + codeStyleSeverity: DiagnosticSeverity.Error); + } + + [MSBuildFact] + public async Task FilesFormattedInCodeStyleSolution_WhenFixingCodeStyleWarnings() + { + await TestFormatWorkspaceAsync( + s_codeStyleSolutionFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 2, + expectedFileCount: 6, + fixCategory: FixCategory.Whitespace | FixCategory.CodeStyle, + codeStyleSeverity: DiagnosticSeverity.Warning); + } + + [MSBuildFact] + public async Task FilesFormattedInCodeStyleSolutionFilter_WhenFixingCodeStyleWarnings() + { + var restoreExitCode = await Utilities.DotNetHelper.PerformRestoreAsync(s_codeStyleSolutionFilterFilePath, _output); + Assert.Equal(0, restoreExitCode); + + await TestFormatWorkspaceAsync( + s_codeStyleSolutionFilterFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 1, + expectedFileCount: 3, + fixCategory: FixCategory.Whitespace | FixCategory.CodeStyle, + codeStyleSeverity: DiagnosticSeverity.Warning); + } + + [MSBuildFact] + public async Task NoFilesFormattedInAnalyzersSolution_WhenNotFixingAnalyzers() + { + await TestFormatWorkspaceAsync( + s_analyzersSolutionFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 0, + expectedFileCount: 7, + fixCategory: FixCategory.Whitespace); + } + + [MSBuildFact] + public async Task FilesFormattedInAnalyzersSolution_WhenFixingAnalyzerErrors() + { + await TestFormatWorkspaceAsync( + s_analyzersSolutionFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 1, + expectedFileCount: 7, + fixCategory: FixCategory.Whitespace | FixCategory.Analyzers, + analyzerSeverity: DiagnosticSeverity.Error); + } + + [MSBuildFact] + public async Task AdditionalDocumentsSavedInAnalyzersSolution_WhenFixingAnalyzerErrors() + { + // Copy solution to temp folder so we can write changes to disk. + var solutionPath = CopyToTempFolder(s_analyzersSolutionPath); + + try + { + // Fix PublicAPI analyzer diagnostics. + await TestFormatWorkspaceAsync( + Path.Combine(solutionPath, "library", "library.csproj"), + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 1, + expectedFileCount: 3, + fixCategory: FixCategory.Analyzers, + analyzerSeverity: DiagnosticSeverity.Warning, + diagnostics: new[] { "RS0016" }, + saveFormattedFiles: true); + + // Verify that changes were persisted to disk. + var unshippedPublicApi = File.ReadAllText(Path.Combine(solutionPath, "library", "PublicAPI.Unshipped.txt")); + Assert.NotEqual(string.Empty, unshippedPublicApi); + } + finally + { + // Cleanup + Directory.Delete(solutionPath, true); + } + } + + [MSBuildFact] + public async Task GeneratorSolution_NoDiagnosticsReported_WhenNotIncludingGenerated() + { + // Copy solution to temp folder so we can write changes to disk. + var solutionPath = CopyToTempFolder(s_generatorSolutionPath); + + try + { + var solutionFilePath = Path.Combine(solutionPath, s_generatorSolutionFileName); + + var buildExitCode = await Utilities.DotNetHelper.PerformBuildAsync(solutionFilePath, _output); + Assert.Equal(0, buildExitCode); + + // Fix PublicAPI analyzer diagnostics. + await TestFormatWorkspaceAsync( + solutionFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: false, + expectedExitCode: 0, + expectedFilesFormatted: 0, + expectedFileCount: 7, + fixCategory: FixCategory.Analyzers, + analyzerSeverity: DiagnosticSeverity.Warning, + diagnostics: new[] { "RS0016" }, + saveFormattedFiles: true); + + // Verify that changes were persisted to disk. + var unshippedPublicApi = File.ReadAllText(Path.Combine(solutionPath, "console_app", "PublicAPI.Unshipped.txt")); + Assert.Equal(string.Empty, unshippedPublicApi); + } + finally + { + try + { + Directory.Delete(solutionPath, true); + } + catch (UnauthorizedAccessException) + { + // The Windows the generator library may still be locked + } + } + } + + [MSBuildFact] + public async Task GeneratorSolution_AdditionalDocumentsUpdated_WhenIncludingGenerated() + { + const string ExpectedPublicApi = @"Greeter +Greeter.Greet() -> void +Greeter.Greeter() -> void"; + + // Copy solution to temp folder so we can write changes to disk. + var solutionPath = CopyToTempFolder(s_generatorSolutionPath); + + try + { + var solutionFilePath = Path.Combine(solutionPath, s_generatorSolutionFileName); + + var buildExitCode = await Utilities.DotNetHelper.PerformBuildAsync(solutionFilePath, _output); + Assert.Equal(0, buildExitCode); + + // Fix PublicAPI analyzer diagnostics. + await TestFormatWorkspaceAsync( + solutionFilePath, + include: EmptyFilesList, + exclude: EmptyFilesList, + includeGenerated: true, + expectedExitCode: 0, + expectedFilesFormatted: 1, + expectedFileCount: 8, + fixCategory: FixCategory.Analyzers, + analyzerSeverity: DiagnosticSeverity.Warning, + diagnostics: new[] { "RS0016" }, + saveFormattedFiles: true); + + // Verify that changes were persisted to disk. + var unshippedPublicApi = File.ReadAllText(Path.Combine(solutionPath, "console_app", "PublicAPI.Unshipped.txt")); + Assert.Equal(ExpectedPublicApi, unshippedPublicApi); + } + finally + { + try + { + Directory.Delete(solutionPath, true); + } + catch (UnauthorizedAccessException) + { + // On Windows the generator library may still be locked + } + } + } + + internal async Task TestFormatWorkspaceAsync( + string workspaceFilePath, + string[] include, + string[] exclude, + bool includeGenerated, + int expectedExitCode, + int expectedFilesFormatted, + int expectedFileCount, + FixCategory fixCategory = FixCategory.Whitespace, + DiagnosticSeverity codeStyleSeverity = DiagnosticSeverity.Error, + DiagnosticSeverity analyzerSeverity = DiagnosticSeverity.Error, + string[] diagnostics = null, + bool noRestore = false, + bool saveFormattedFiles = false) + { + var currentDirectory = Environment.CurrentDirectory; + Environment.CurrentDirectory = TestProjectsPathHelper.GetProjectsDirectory(); + + var workspacePath = Path.GetFullPath(workspaceFilePath); + + WorkspaceType workspaceType; + if (Directory.Exists(workspacePath)) + { + workspaceType = WorkspaceType.Folder; + } + else + { + workspaceType = workspacePath.EndsWith("proj") + ? WorkspaceType.Project + : WorkspaceType.Solution; + } + + var logger = new TestLogger(); + var msBuildPath = MSBuildRegistrar.RegisterInstance(); + + logger.LogTrace(Resources.Using_msbuildexe_located_in_0, msBuildPath); + + var fileMatcher = SourceFileMatcher.CreateMatcher(include, exclude); + var formatOptions = new FormatOptions( + workspacePath, + workspaceType, + noRestore, + LogLevel.Trace, + fixCategory, + codeStyleSeverity, + analyzerSeverity, + diagnostics?.ToImmutableHashSet() ?? ImmutableHashSet.Empty, + ExcludeDiagnostics: ImmutableHashSet.Empty, + saveFormattedFiles, + ChangesAreErrors: false, + fileMatcher, + ReportPath: string.Empty, + IncludeGeneratedFiles: includeGenerated, + BinaryLogPath: null); + var formatResult = await CodeFormatter.FormatWorkspaceAsync(formatOptions, logger, CancellationToken.None); + Environment.CurrentDirectory = currentDirectory; + + var log = logger.GetLog(); + + try + { + Assert.Equal(expectedExitCode, formatResult.ExitCode); + Assert.Equal(expectedFilesFormatted, formatResult.FilesFormatted); + Assert.Equal(expectedFileCount, formatResult.FileCount); + } + catch + { + _output.WriteLine(log); + throw; + } + + return log; + } + + /// + /// Copies the specified folder to the temp folder and returns the path. + /// + private static string CopyToTempFolder(string sourcePath) + { + var fullPath = Path.GetFullPath(sourcePath, TestProjectsPathHelper.GetProjectsDirectory()); + var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + + DirectoryCopy(fullPath, tempPath, true); + + return tempPath; + + static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs) + { + // Get the subdirectories for the specified directory. + var dir = new DirectoryInfo(sourceDirName); + + if (!dir.Exists) + { + throw new DirectoryNotFoundException( + "Source directory does not exist or could not be found: " + + sourceDirName); + } + + var dirs = dir.GetDirectories(); + + // If the destination directory doesn't exist, create it. + Directory.CreateDirectory(destDirName); + + // Get the files in the directory and copy them to the new location. + var files = dir.GetFiles(); + foreach (var file in files) + { + var tempPath = Path.Combine(destDirName, file.Name); + file.CopyTo(tempPath, false); + } + + // If copying subdirectories, copy them and their contents to new location. + if (copySubDirs) + { + foreach (var subdir in dirs) + { + var tempPath = Path.Combine(destDirName, subdir.Name); + DirectoryCopy(subdir.FullName, tempPath, copySubDirs); + } + } + } + } + } +} diff --git a/test/dotnet-format.Tests/tests/Extensions/ExportProviderExtensions.cs b/test/dotnet-format.Tests/tests/Extensions/ExportProviderExtensions.cs new file mode 100644 index 000000000000..909d27c75355 --- /dev/null +++ b/test/dotnet-format.Tests/tests/Extensions/ExportProviderExtensions.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Composition; +using System.Composition.Hosting.Core; +using System.Reflection; +using Microsoft.VisualStudio.Composition; + +namespace Microsoft.CodeAnalysis.Tools.Tests +{ + internal static class ExportProviderExtensions + { + public static CompositionContext AsCompositionContext(this ExportProvider exportProvider) + { + return new CompositionContextShim(exportProvider); + } + + private class CompositionContextShim : CompositionContext + { + private readonly ExportProvider _exportProvider; + + public CompositionContextShim(ExportProvider exportProvider) + { + _exportProvider = exportProvider; + } + + public override bool TryGetExport(CompositionContract contract, out object export) + { + var importMany = contract.MetadataConstraints.Contains(new KeyValuePair("IsImportMany", true)); + var (contractType, metadataType, isArray) = GetContractType(contract.ContractType, importMany); + + if (metadataType != null) + { + var methodInfo = (from method in _exportProvider.GetType().GetTypeInfo().GetMethods() + where method.Name == nameof(ExportProvider.GetExports) + where method.IsGenericMethod && method.GetGenericArguments().Length == 2 + where method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == typeof(string) + select method).Single(); + var parameterizedMethod = methodInfo.MakeGenericMethod(contractType, metadataType); + export = parameterizedMethod.Invoke(_exportProvider, new[] { contract.ContractName }); + } + else if (!isArray) + { + var methodInfo = (from method in _exportProvider.GetType().GetTypeInfo().GetMethods() + where method.Name == nameof(ExportProvider.GetExports) + where method.IsGenericMethod && method.GetGenericArguments().Length == 1 + where method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == typeof(string) + select method).Single(); + var parameterizedMethod = methodInfo.MakeGenericMethod(contractType); + export = parameterizedMethod.Invoke(_exportProvider, new[] { contract.ContractName }); + } + else + { + var methodInfo = (from method in _exportProvider.GetType().GetTypeInfo().GetMethods() + where method.Name == nameof(ExportProvider.GetExportedValues) + where method.IsGenericMethod && method.GetGenericArguments().Length == 1 + where method.GetParameters().Length == 0 + select method).Single(); + var parameterizedMethod = methodInfo.MakeGenericMethod(contractType); + export = parameterizedMethod.Invoke(_exportProvider, null); + } + + return true; + } + + private (Type exportType, Type metadataType, bool isArray) GetContractType(Type contractType, bool importMany) + { + if (importMany && contractType.BaseType == typeof(Array)) + { + return (contractType.GetElementType(), null, true); + } + + if (importMany && contractType.IsConstructedGenericType) + { + if (contractType.GetGenericTypeDefinition() == typeof(IList<>) + || contractType.GetGenericTypeDefinition() == typeof(ICollection<>) + || contractType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + contractType = contractType.GenericTypeArguments[0]; + } + } + + if (contractType.IsConstructedGenericType) + { + if (contractType.GetGenericTypeDefinition() == typeof(Lazy<>)) + { + return (contractType.GenericTypeArguments[0], null, false); + } + else if (contractType.GetGenericTypeDefinition() == typeof(Lazy<,>)) + { + return (contractType.GenericTypeArguments[0], contractType.GenericTypeArguments[1], false); + } + else + { + throw new NotSupportedException(); + } + } + + throw new NotSupportedException(); + } + } + } +} diff --git a/test/dotnet-format.Tests/tests/Formatters/AbstractFormatterTests.cs b/test/dotnet-format.Tests/tests/Formatters/AbstractFormatterTests.cs new file mode 100644 index 000000000000..dab3a79ed9c4 --- /dev/null +++ b/test/dotnet-format.Tests/tests/Formatters/AbstractFormatterTests.cs @@ -0,0 +1,425 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Tools.Formatters; +using Microsoft.CodeAnalysis.Tools.Tests.Utilities; +using Microsoft.CodeAnalysis.Tools.Utilities; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.Composition; + +#nullable enable + +namespace Microsoft.CodeAnalysis.Tools.Tests.Formatters +{ + public abstract class AbstractFormatterTest + { + private static MetadataReference MicrosoftVisualBasicReference => MetadataReference.CreateFromFile(typeof(Microsoft.VisualBasic.Strings).Assembly.Location); + + private static Lazy ExportProviderFactory { get; } + + static AbstractFormatterTest() + { + ExportProviderFactory = new Lazy( + () => + { + var discovery = new AttributedPartDiscovery(Resolver.DefaultInstance, isNonPublicSupported: true); + var parts = Task.Run(() => discovery.CreatePartsAsync(MefHostServices.DefaultAssemblies)).GetAwaiter().GetResult(); + var catalog = ComposableCatalog.Create(Resolver.DefaultInstance).AddParts(parts); + + var configuration = CompositionConfiguration.Create(catalog); + var runtimeComposition = RuntimeComposition.CreateRuntimeComposition(configuration); + return runtimeComposition.CreateExportProviderFactory(); + }, + LazyThreadSafetyMode.ExecutionAndPublication); + } + + protected virtual ReferenceAssemblies ReferenceAssemblies => ReferenceAssemblies.NetStandard.NetStandard20; + + protected virtual string DefaultFilePathPrefix => "Test"; + + protected virtual string DefaultTestProjectName => "TestProject"; + + // This folder path needs to appear rooted when adding the AnalyzerConfigDocument. + // We achieve this by prepending a directory separator. + protected virtual string DefaultFolderPath => Path.DirectorySeparatorChar + DefaultTestProjectName; + + protected virtual string DefaultTestProjectPath => Path.Combine(DefaultFolderPath, $"{DefaultTestProjectName}.{DefaultFileExt}proj"); + + protected virtual string DefaultEditorConfigPath => Path.Combine(DefaultFolderPath, ".editorconfig"); + + protected virtual string DefaultFilePath => Path.Combine(DefaultFolderPath, $"{DefaultFilePathPrefix}0.{DefaultFileExt}"); + + protected abstract string DefaultFileExt { get; } + + private protected abstract ICodeFormatter Formatter { get; } + + protected ITestOutputHelper? TestOutputHelper { get; set; } + + protected AbstractFormatterTest() + { + TestState = new SolutionState("Test", Language, DefaultFilePathPrefix, DefaultFileExt); + } + + /// + /// Gets the language name used for the test. + /// + public abstract string Language { get; } + + public SolutionState TestState { get; } + + private protected string ToEditorConfig(IReadOnlyDictionary editorConfig) => $@"root = true + +[*.{DefaultFileExt}] +{string.Join(Environment.NewLine, editorConfig.Select(kvp => $"{kvp.Key} = {kvp.Value}"))} +"; + + private protected Task AssertNoReportedFileChangesAsync( + string code, + IReadOnlyDictionary editorConfig, + Encoding? encoding = null, + FixCategory fixCategory = FixCategory.Whitespace, + IEnumerable? analyzerReferences = null, + DiagnosticSeverity codeStyleSeverity = DiagnosticSeverity.Error, + DiagnosticSeverity analyzerSeverity = DiagnosticSeverity.Error, + string[]? diagnostics = null) + { + return AssertNoReportedFileChangesAsync(code, ToEditorConfig(editorConfig), encoding, fixCategory, analyzerReferences, codeStyleSeverity, analyzerSeverity, diagnostics); + } + + private protected async Task AssertNoReportedFileChangesAsync( + string code, + string editorConfig, + Encoding? encoding = null, + FixCategory fixCategory = FixCategory.Whitespace, + IEnumerable? analyzerReferences = null, + DiagnosticSeverity codeStyleSeverity = DiagnosticSeverity.Error, + DiagnosticSeverity analyzerSeverity = DiagnosticSeverity.Error, + string[]? diagnostics = null) + { + var (formattedText, formattedFiles, logger) = await ApplyFormatterAsync(code, editorConfig, encoding, fixCategory, analyzerReferences, codeStyleSeverity, analyzerSeverity, diagnostics); + + try + { + // Ensure the code is unchanged + Assert.Equal(code, formattedText.ToString()); + + // Ensure no non-fixable diagnostics were reported + Assert.Empty(formattedFiles); + } + catch + { + TestOutputHelper?.WriteLine(logger.GetLog()); + throw; + } + + return formattedText; + } + + private protected Task AssertCodeUnchangedAsync( + string code, + IReadOnlyDictionary editorConfig, + Encoding? encoding = null, + FixCategory fixCategory = FixCategory.Whitespace, + IEnumerable? analyzerReferences = null, + DiagnosticSeverity codeStyleSeverity = DiagnosticSeverity.Error, + DiagnosticSeverity analyzerSeverity = DiagnosticSeverity.Error, + string[]? diagnostics = null) + { + return AssertCodeUnchangedAsync(code, ToEditorConfig(editorConfig), encoding, fixCategory, analyzerReferences, codeStyleSeverity, analyzerSeverity, diagnostics); + } + + private protected async Task AssertCodeUnchangedAsync( + string code, + string editorConfig, + Encoding? encoding = null, + FixCategory fixCategory = FixCategory.Whitespace, + IEnumerable? analyzerReferences = null, + DiagnosticSeverity codeStyleSeverity = DiagnosticSeverity.Error, + DiagnosticSeverity analyzerSeverity = DiagnosticSeverity.Error, + string[]? diagnostics = null) + { + var (formattedText, _, logger) = await ApplyFormatterAsync(code, editorConfig, encoding, fixCategory, analyzerReferences, codeStyleSeverity, analyzerSeverity, diagnostics); + + try + { + // Ensure the code is unchanged + Assert.Equal(code, formattedText.ToString()); + } + catch + { + TestOutputHelper?.WriteLine(logger.GetLog()); + throw; + } + + return formattedText; + } + + private protected Task AssertCodeChangedAsync( + string testCode, + string expectedCode, + IReadOnlyDictionary editorConfig, + Encoding? encoding = null, + FixCategory fixCategory = FixCategory.Whitespace, + IEnumerable? analyzerReferences = null, + DiagnosticSeverity codeStyleSeverity = DiagnosticSeverity.Error, + DiagnosticSeverity analyzerSeverity = DiagnosticSeverity.Error, + string[]? diagnostics = null) + { + return AssertCodeChangedAsync(testCode, expectedCode, ToEditorConfig(editorConfig), encoding, fixCategory, analyzerReferences, codeStyleSeverity, analyzerSeverity, diagnostics); + } + + private protected async Task AssertCodeChangedAsync( + string testCode, + string expectedCode, + string editorConfig, + Encoding? encoding = null, + FixCategory fixCategory = FixCategory.Whitespace, + IEnumerable? analyzerReferences = null, + DiagnosticSeverity codeStyleSeverity = DiagnosticSeverity.Error, + DiagnosticSeverity analyzerSeverity = DiagnosticSeverity.Error, + string[]? diagnostics = null) + { + var (formattedText, _, logger) = await ApplyFormatterAsync(testCode, editorConfig, encoding, fixCategory, analyzerReferences, codeStyleSeverity, analyzerSeverity, diagnostics); + + try + { + Assert.Equal(expectedCode, formattedText.ToString()); + } + catch + { + TestOutputHelper?.WriteLine(logger.GetLog()); + throw; + } + + return formattedText; + } + + private protected async Task<(SourceText FormattedText, List FormattedFiles, TestLogger Logger)> ApplyFormatterAsync( + string code, + string editorConfig, + Encoding? encoding = null, + FixCategory fixCategory = FixCategory.Whitespace, + IEnumerable? analyzerReferences = null, + DiagnosticSeverity codeStyleSeverity = DiagnosticSeverity.Error, + DiagnosticSeverity analyzerSeverity = DiagnosticSeverity.Error, + string[]? diagnostics = null) + { + var text = SourceText.From(code, encoding ?? Encoding.UTF8); + TestState.Sources.Add(text); + + var (workspace, solution) = await GetSolutionAsync(TestState.Sources.ToArray(), TestState.AdditionalFiles.ToArray(), TestState.AdditionalReferences.ToArray(), editorConfig, analyzerReferences); + var project = solution.Projects.Single(); + var document = project.Documents.Single(); + + var fileMatcher = SourceFileMatcher.CreateMatcher(new[] { document.FilePath! }, exclude: Array.Empty()); + var formatOptions = new FormatOptions( + WorkspaceFilePath: project.FilePath!, + WorkspaceType: WorkspaceType.Solution, + NoRestore: false, + LogLevel: LogLevel.Trace, + fixCategory, + codeStyleSeverity, + analyzerSeverity, + (diagnostics ?? Array.Empty()).ToImmutableHashSet(), + ExcludeDiagnostics: ImmutableHashSet.Empty, + SaveFormattedFiles: true, + ChangesAreErrors: false, + fileMatcher, + ReportPath: string.Empty, + IncludeGeneratedFiles: false, + BinaryLogPath: null); + + var pathsToFormat = GetOnlyFileToFormat(solution); + + var logger = new TestLogger(); + var formattedFiles = new List(); + + var formattedSolution = await Formatter.FormatAsync(workspace, solution, pathsToFormat, formatOptions, logger, formattedFiles, default); + var formattedDocument = GetOnlyDocument(formattedSolution); + var formattedText = await formattedDocument.GetTextAsync(); + + return (formattedText, formattedFiles, logger); + } + + /// + /// Gets the only . + /// + /// A Solution containing a single Project containing a single Document. + /// The only document id. + internal ImmutableArray GetOnlyFileToFormat(Solution solution) => ImmutableArray.Create(GetOnlyDocument(solution).Id); + + /// + /// Gets the only contained within the only within the . + /// + /// A Solution containing a single Project containing a single Document. + /// The document contained within. + public Document GetOnlyDocument(Solution solution) => solution.Projects.Single().Documents.Single(); + + /// + /// Gets the collection of inputs to provide to the XML documentation resolver. + /// + /// + /// Files in this collection may be referenced via <include> elements in documentation + /// comments. + /// + public Dictionary XmlReferences { get; } = new Dictionary(); + + /// + /// Given an array of strings as sources and a language, turn them into a and return the + /// solution. + /// + /// Classes in the form of strings. + /// Additional documents to include in the project. + /// Additional metadata references to include in the project. + /// The .editorconfig to apply to this solution. + /// A solution containing a project with the specified sources and additional files. + private protected async Task<(Workspace workspace, Solution solution)> GetSolutionAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, IReadOnlyDictionary editorConfig, IEnumerable? analyzerReferences = null) + { + return await GetSolutionAsync(sources, additionalFiles, additionalMetadataReferences, ToEditorConfig(editorConfig), analyzerReferences); + } + + /// + /// Given an array of strings as sources and a language, turn them into a and return the + /// solution. + /// + /// Classes in the form of strings. + /// Additional documents to include in the project. + /// Additional metadata references to include in the project. + /// The .editorconfig to apply to this solution. + /// A solution containing a project with the specified sources and additional files. + private protected async Task<(Workspace workspace, Solution solution)> GetSolutionAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, string editorConfig, IEnumerable? analyzerReferences = null) + { + analyzerReferences ??= Enumerable.Empty(); + var (workspace, project) = await CreateProjectAsync(sources, additionalFiles, additionalMetadataReferences, analyzerReferences, Language, SourceText.From(editorConfig, Encoding.UTF8)); + return (workspace, project.Solution); + } + + /// + /// Create a project using the input strings as sources. + /// + /// + /// This method first creates a by calling , and then + /// applies compilation options to the project by calling . + /// + /// Classes in the form of strings. + /// Additional documents to include in the project. + /// Additional metadata references to include in the project. + /// The language the source classes are in. Values may be taken from the + /// class. + /// The .editorconfig to apply to this project. + /// A created out of the s created from the source + /// strings. + protected async Task<(Workspace workspace, Project project)> CreateProjectAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, IEnumerable analyzerReferences, string language, SourceText editorConfigText) + { + language ??= Language; + return await CreateProjectImplAsync(sources, additionalFiles, additionalMetadataReferences, analyzerReferences, language, editorConfigText); + } + + /// + /// Create a project using the input strings as sources. + /// + /// Classes in the form of strings. + /// Additional documents to include in the project. + /// Additional metadata references to include in the project. + /// The language the source classes are in. Values may be taken from the + /// class. + /// The .editorconfig to apply to this project. + /// A created out of the s created from the source + /// strings. + protected virtual async Task<(Workspace workspace, Project project)> CreateProjectImplAsync((string filename, SourceText content)[] sources, (string filename, SourceText content)[] additionalFiles, MetadataReference[] additionalMetadataReferences, IEnumerable analyzerReferences, string language, SourceText editorConfigText) + { + var projectId = ProjectId.CreateNewId(debugName: DefaultTestProjectName); + var (workspace, solution) = await CreateSolutionAsync(projectId, language, editorConfigText); + solution = solution + .AddAnalyzerReferences(projectId, analyzerReferences) + .AddMetadataReferences(projectId, additionalMetadataReferences); + + for (var i = 0; i < sources.Length; i++) + { + (var newFileName, var source) = sources[i]; + var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); + solution = solution.AddDocument(documentId, newFileName, source, filePath: Path.Combine(DefaultFolderPath, newFileName)); + } + + for (var i = 0; i < additionalFiles.Length; i++) + { + (var newFileName, var source) = additionalFiles[i]; + var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); + solution = solution.AddAdditionalDocument(documentId, newFileName, source); + } + + return (workspace, solution.GetProject(projectId)!); + } + + /// + /// Creates a solution that will be used as parent for the sources that need to be checked. + /// + /// The project identifier to use. + /// The language for which the solution is being created. + /// The .editorconfig to apply to this solution. + /// The created solution. + protected virtual async Task<(Workspace workspace, Solution solution)> CreateSolutionAsync(ProjectId projectId, string language, SourceText editorConfigText) + { + var xmlReferenceResolver = new TestXmlReferenceResolver(); + foreach (var xmlReference in XmlReferences) + { + xmlReferenceResolver.XmlReferences.Add(xmlReference.Key, xmlReference.Value); + } + + var compilationOptions = CreateCompilationOptions() + .WithXmlReferenceResolver(xmlReferenceResolver) + .WithAssemblyIdentityComparer(ReferenceAssemblies.AssemblyIdentityComparer); + + var parseOptions = CreateParseOptions(); + var referenceAssemblies = await ReferenceAssemblies.ResolveAsync(language, CancellationToken.None); + + var editorConfigDocument = DocumentInfo.Create( + DocumentId.CreateNewId(projectId, DefaultEditorConfigPath), + name: DefaultEditorConfigPath, + loader: TextLoader.From(TextAndVersion.Create(editorConfigText, VersionStamp.Create())), + filePath: DefaultEditorConfigPath); + + var projectInfo = ProjectInfo.Create( + projectId, + VersionStamp.Create(), + name: DefaultTestProjectName, + assemblyName: DefaultTestProjectName, + language, + filePath: DefaultTestProjectPath, + outputFilePath: Path.ChangeExtension(DefaultTestProjectPath, "dll"), + compilationOptions: compilationOptions, + parseOptions: parseOptions, + metadataReferences: referenceAssemblies, + isSubmission: false) + .WithDefaultNamespace(DefaultTestProjectName) + .WithAnalyzerConfigDocuments(ImmutableArray.Create(editorConfigDocument)); + + var workspace = CreateWorkspace(); + var solution = workspace + .CurrentSolution + .AddProject(projectInfo); + + if (language == LanguageNames.VisualBasic) + { + solution = solution.AddMetadataReference(projectId, MicrosoftVisualBasicReference); + } + + return (workspace, solution); + } + + public virtual AdhocWorkspace CreateWorkspace() + { + var exportProvider = ExportProviderFactory.Value.CreateExportProvider(); + var host = MefHostServices.Create(exportProvider.AsCompositionContext()); + return new AdhocWorkspace(host); + } + + protected abstract CompilationOptions CreateCompilationOptions(); + + protected abstract ParseOptions CreateParseOptions(); + } +} diff --git a/test/dotnet-format.Tests/tests/Formatters/CSharpFormatterTests.cs b/test/dotnet-format.Tests/tests/Formatters/CSharpFormatterTests.cs new file mode 100644 index 000000000000..1c77af21c078 --- /dev/null +++ b/test/dotnet-format.Tests/tests/Formatters/CSharpFormatterTests.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.CSharp; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Formatters +{ + public abstract class CSharpFormatterTests : AbstractFormatterTest + { + protected override string DefaultFileExt => "cs"; + + public override string Language => LanguageNames.CSharp; + + protected override CompilationOptions CreateCompilationOptions() + => new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true); + + protected override ParseOptions CreateParseOptions() + => new CSharpParseOptions(LanguageVersion.Default, DocumentationMode.Diagnose); + } +} diff --git a/test/dotnet-format.Tests/tests/Formatters/CharsetFormatterTests.cs b/test/dotnet-format.Tests/tests/Formatters/CharsetFormatterTests.cs new file mode 100644 index 000000000000..a0de640bc6de --- /dev/null +++ b/test/dotnet-format.Tests/tests/Formatters/CharsetFormatterTests.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Tools.Formatters; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Formatters +{ + public class CharsetFormatterTests : CSharpFormatterTests + { + private protected override ICodeFormatter Formatter => new CharsetFormatter(); + + public CharsetFormatterTests(ITestOutputHelper output) + { + TestOutputHelper = output; + } + + [Theory] + [InlineData("latin1", "utf-8")] + [InlineData("latin1", "utf-8-bom")] + [InlineData("latin1", "utf-16be")] + [InlineData("latin1", "utf-16le")] + [InlineData("utf-8", "latin1")] + [InlineData("utf-8", "utf-8-bom")] + [InlineData("utf-8", "utf-16be")] + [InlineData("utf-8", "utf-16le")] + [InlineData("utf-8-bom", "latin1")] + [InlineData("utf-8-bom", "utf-8")] + [InlineData("utf-8-bom", "utf-16be")] + [InlineData("utf-8-bom", "utf-16le")] + [InlineData("utf-16be", "latin1")] + [InlineData("utf-16be", "utf-8")] + [InlineData("utf-16be", "utf-8-bom")] + [InlineData("utf-16be", "utf-16le")] + [InlineData("utf-16le", "latin1")] + [InlineData("utf-16le", "utf-8")] + [InlineData("utf-16le", "utf-8-bom")] + [InlineData("utf-16le", "utf-16be")] + public async Task TestCharsetWrong_CharsetFixed(string codeValue, string expectedValue) + { + var codeEncoding = CharsetFormatter.GetCharset(codeValue); + var expectedEncoding = CharsetFormatter.GetCharset(expectedValue); + + // Use unicode to ensure that "latin1" and "utf8" don't look equivalent. + var testCode = "class 🤵 { }"; + + var editorConfig = new Dictionary() + { + + ["charset"] = expectedValue, + }; + + var formattedText = await AssertCodeUnchangedAsync(testCode, editorConfig, codeEncoding); + + Assert.Equal(expectedEncoding, formattedText.Encoding); + } + + [Fact] + public async Task TestCharsetNotSpecified_NoChange() + { + // This encoding is not supported by .editorconfig, so if it roundtrips then there was no change. + var codeEncoding = Encoding.UTF32; + + var testCode = "class 🤵 { }"; + + var editorConfig = new Dictionary() + { + }; + + var formattedText = await AssertCodeUnchangedAsync(testCode, editorConfig, codeEncoding); + + Assert.Equal(codeEncoding, formattedText.Encoding); + } + } +} diff --git a/test/dotnet-format.Tests/tests/Formatters/EndOfLineFormatterTests.cs b/test/dotnet-format.Tests/tests/Formatters/EndOfLineFormatterTests.cs new file mode 100644 index 000000000000..1a5a80e02cca --- /dev/null +++ b/test/dotnet-format.Tests/tests/Formatters/EndOfLineFormatterTests.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Tools.Formatters; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Formatters +{ + public class EndOfLineFormatterTests : CSharpFormatterTests + { + private protected override ICodeFormatter Formatter => new EndOfLineFormatter(); + + public EndOfLineFormatterTests(ITestOutputHelper output) + { + TestOutputHelper = output; + } + + [Theory] + [InlineData("\n", "\n", "lf")] + [InlineData("\r\n", "\n", "lf")] + [InlineData("\r", "\n", "lf")] + [InlineData("\n", "\r\n", "crlf")] + [InlineData("\r\n", "\r\n", "crlf")] + [InlineData("\r", "\r\n", "crlf")] + [InlineData("\n", "\r", "cr")] + [InlineData("\r\n", "\r", "cr")] + [InlineData("\r", "\r", "cr")] + public async Task TestEndOfLine_NoFinalNewline(string codeNewline, string expectedNewline, string endOfLine) + { + var testCode = $"class C{codeNewline}{{{codeNewline}}}"; + + var expectedCode = $"class C{expectedNewline}{{{expectedNewline}}}"; + + var editorConfig = new Dictionary() + { + ["end_of_line"] = endOfLine, + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig); + } + + [Theory] + [InlineData("\n", "\n", "lf")] + [InlineData("\r\n", "\n", "lf")] + [InlineData("\r", "\n", "lf")] + [InlineData("\n", "\r\n", "crlf")] + [InlineData("\r\n", "\r\n", "crlf")] + [InlineData("\r", "\r\n", "crlf")] + [InlineData("\n", "\r", "cr")] + [InlineData("\r\n", "\r", "cr")] + [InlineData("\r", "\r", "cr")] + public async Task TestEndOfLine_WithFinalNewline(string codeNewline, string expectedNewline, string endOfLine) + { + var testCode = $"class C{codeNewline}{{{codeNewline}}}{codeNewline}"; + + var expectedCode = $"class C{expectedNewline}{{{expectedNewline}}}{expectedNewline}"; + + var editorConfig = new Dictionary() + { + ["end_of_line"] = endOfLine, + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig); + } + + [Theory] + [InlineData("\n")] + [InlineData("\r\n")] + [InlineData("\r")] + public async Task TestEndOfLine_AndNoSetting_NoChanges(string codeNewline) + { + var testCode = $"class C{codeNewline}{{{codeNewline}}}{codeNewline}"; + + var editorConfig = new Dictionary(); + + await AssertCodeUnchangedAsync(testCode, editorConfig); + } + } +} diff --git a/test/dotnet-format.Tests/tests/Formatters/FinalNewlineFormatterTests.cs b/test/dotnet-format.Tests/tests/Formatters/FinalNewlineFormatterTests.cs new file mode 100644 index 000000000000..81a70ab0853d --- /dev/null +++ b/test/dotnet-format.Tests/tests/Formatters/FinalNewlineFormatterTests.cs @@ -0,0 +1,193 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Tools.Formatters; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Formatters +{ + public class FinalNewlineFormatterTests : CSharpFormatterTests + { + private protected override ICodeFormatter Formatter => new FinalNewlineFormatter(); + + public FinalNewlineFormatterTests(ITestOutputHelper output) + { + TestOutputHelper = output; + } + + [Fact] + public async Task WhenFinalNewlineUnspecified_AndFinalNewlineMissing_NoChange() + { + var code = @" +class C +{ +}"; + + var editorConfig = new Dictionary() + { + ["end_of_line"] = "crlf", + }; + + await AssertCodeUnchangedAsync(code, editorConfig); + } + + [Fact] + public async Task WhenFinalNewlineUnspecified_AndFinalNewlineExits_NoChange() + { + var code = @" +class C +{ +} +"; + + var editorConfig = new Dictionary() + { + ["end_of_line"] = "crlf", + }; + + await AssertCodeUnchangedAsync(code, editorConfig); + } + + [Fact] + public async Task WhenFinalNewlineRequired_AndEndOfLineIsLineFeed_LineFeedAdded() + { + var testCode = "class C\n{\n}"; + + var expectedCode = "class C\n{\n}\n"; + + var editorConfig = new Dictionary() + { + ["insert_final_newline"] = "true", + ["end_of_line"] = "lf", + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig); + } + + [Fact] + public async Task WhenFinalNewlineRequired_AndEndOfLineIsCarriageReturnLineFeed_CarriageReturnLineFeedAdded() + { + var testCode = "class C\r\n{\r\n}"; + + var expectedCode = "class C\r\n{\r\n}\r\n"; + + var editorConfig = new Dictionary() + { + ["insert_final_newline"] = "true", + ["end_of_line"] = "crlf", + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig); + } + + [Fact] + public async Task WhenFinalNewlineRequired_AndEndOfLineIsCarriageReturn_CarriageReturnAdded() + { + var testCode = "class C\r{\r}"; + + var expectedCode = "class C\r{\r}\r"; + + var editorConfig = new Dictionary() + { + ["insert_final_newline"] = "true", + ["end_of_line"] = "cr", + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig); + } + [Fact] + public async Task WhenFinalNewlineRequired_AndFinalNewlineExits_NoChange() + { + var code = @" +class C +{ +} +"; + + var editorConfig = new Dictionary() + { + ["insert_final_newline"] = "true", + ["end_of_line"] = "crlf", + }; + + await AssertCodeUnchangedAsync(code, editorConfig); + } + + [Fact] + public async Task WhenFinalNewlineUnwanted_AndFinalNewlineExists_CarriageReturnLineFeedRemoved() + { + var testCode = "class C\r\n{\r\n}\r\n\r\n\r\n"; + + var expectedCode = "class C\r\n{\r\n}"; + + var editorConfig = new Dictionary() + { + ["insert_final_newline"] = "false", + ["end_of_line"] = "crlf", + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig); + } + + [Fact] + public async Task WhenFinalNewlineUnwanted_AndFinalNewlineExists_LineFeedRemoved() + { + var testCode = "class C\n{\n}\n\n\n"; + + var expectedCode = "class C\n{\n}"; + + var editorConfig = new Dictionary() + { + ["insert_final_newline"] = "false", + ["end_of_line"] = "crlf", + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig); + } + + [Fact] + public async Task WhenFinalNewlineUnwanted_AndFinalNewlineExists_CarriageReturnRemoved() + { + var testCode = "class C\r{\r}\r\r\r"; + + var expectedCode = "class C\r{\r}"; + + var editorConfig = new Dictionary() + { + ["insert_final_newline"] = "false", + ["end_of_line"] = "crlf", + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig); + } + + [Fact] + public async Task WhenFinalNewlineUnwanted_AndFinalNewlineMissing_NoChange() + { + var code = @" +class C +{ +}"; + + var editorConfig = new Dictionary() + { + ["insert_final_newline"] = "false", + ["end_of_line"] = "crlf", + }; + + await AssertCodeUnchangedAsync(code, editorConfig); + } + + [Fact] + public async Task WhenFinalNewlineUnwanted_AndFileIsEmpty_NoChange() + { + var code = @""; + + var editorConfig = new Dictionary() + { + ["insert_final_newline"] = "false", + ["end_of_line"] = "crlf", + }; + + await AssertCodeUnchangedAsync(code, editorConfig); + } + } +} diff --git a/test/dotnet-format.Tests/tests/Formatters/FormattedFilesTests.cs b/test/dotnet-format.Tests/tests/Formatters/FormattedFilesTests.cs new file mode 100644 index 000000000000..16233eff7fed --- /dev/null +++ b/test/dotnet-format.Tests/tests/Formatters/FormattedFilesTests.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Tools.Formatters; +using Microsoft.CodeAnalysis.Tools.Tests.Utilities; +using Microsoft.CodeAnalysis.Tools.Utilities; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Formatters +{ + public class FormattedFilesTests : CSharpFormatterTests + { + private protected override ICodeFormatter Formatter => new FinalNewlineFormatter(); + + private Dictionary EditorConfig => new Dictionary() + { + ["insert_final_newline"] = "true", + ["end_of_line"] = "lf", + }; + + public FormattedFilesTests(ITestOutputHelper output) + { + TestOutputHelper = output; + } + + [Fact] + public async Task ReturnsItem_WhenFileFormatted() + { + var testCode = "class C\n{\n}"; + + var result = await TestFormattedFiles(testCode); + + Assert.Single(result); + } + + [Fact] + public async Task ReturnsEmptyList_WhenNoFilesFormatted() + { + var testCode = "class C\n{\n}\n"; + + var result = await TestFormattedFiles(testCode); + + Assert.Empty(result); + } + + private async Task> TestFormattedFiles(string testCode) + { + var text = SourceText.From(testCode, Encoding.UTF8); + TestState.Sources.Add(text); + + var (workspace, solution) = await GetSolutionAsync(TestState.Sources.ToArray(), TestState.AdditionalFiles.ToArray(), TestState.AdditionalReferences.ToArray(), EditorConfig); + var project = solution.Projects.Single(); + var document = project.Documents.Single(); + + var fileMatcher = SourceFileMatcher.CreateMatcher(new[] { document.FilePath }, exclude: Array.Empty()); + var formatOptions = new FormatOptions( + WorkspaceFilePath: project.FilePath, + WorkspaceType: WorkspaceType.Folder, + NoRestore: false, + LogLevel: LogLevel.Trace, + FixCategory: FixCategory.Whitespace, + CodeStyleSeverity: DiagnosticSeverity.Error, + AnalyzerSeverity: DiagnosticSeverity.Error, + Diagnostics: ImmutableHashSet.Empty, + ExcludeDiagnostics: ImmutableHashSet.Empty, + SaveFormattedFiles: false, + ChangesAreErrors: false, + fileMatcher, + ReportPath: string.Empty, + IncludeGeneratedFiles: false, + BinaryLogPath: null); + + var pathsToFormat = GetOnlyFileToFormat(solution); + + var formattedFiles = new List(); + await Formatter.FormatAsync(workspace, solution, pathsToFormat, formatOptions, new TestLogger(), formattedFiles, default); + + return formattedFiles; + } + } +} diff --git a/test/dotnet-format.Tests/tests/Formatters/OrganizeImportsFormatterTests.cs b/test/dotnet-format.Tests/tests/Formatters/OrganizeImportsFormatterTests.cs new file mode 100644 index 000000000000..bee88c6aa158 --- /dev/null +++ b/test/dotnet-format.Tests/tests/Formatters/OrganizeImportsFormatterTests.cs @@ -0,0 +1,162 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Tools.Formatters; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Formatters +{ + public class OrganizeImportsFormatterTests : CSharpFormatterTests + { + private protected override ICodeFormatter Formatter => new OrganizeImportsFormatter(); + + public OrganizeImportsFormatterTests(ITestOutputHelper output) + { + TestOutputHelper = output; + } + + [Fact] + public async Task WhenOptionsDisabled_AndImportsNotSorted_ImportsSorted() + { + var testCode = @" +using Microsoft.CodeAnalysis; +using System.Linq; +using System; + +class C +{ +}"; + + var expectedCode = @" +using Microsoft.CodeAnalysis; +using System; +using System.Linq; + +class C +{ +}"; + + var editorConfig = new Dictionary() + { + ["end_of_line"] = EndOfLineFormatter.GetEndOfLineOption(Environment.NewLine), + ["dotnet_sort_system_directives_first"] = "false", + ["dotnet_separate_import_directive_groups"] = "false" + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig); + } + + [Fact] + public async Task WhenSystemDirectivesFirst_AndImportsNotSorted_ImportsSorted() + { + var testCode = @" +using Microsoft.CodeAnalysis; +using System.Linq; +using System; + +class C +{ +}"; + + var expectedCode = @" +using System; +using System.Linq; +using Microsoft.CodeAnalysis; + +class C +{ +}"; + + var editorConfig = new Dictionary() + { + ["end_of_line"] = EndOfLineFormatter.GetEndOfLineOption(Environment.NewLine), + ["dotnet_sort_system_directives_first"] = "true", + ["dotnet_separate_import_directive_groups"] = "false" + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig); + } + + [Fact] + public async Task WhenImportGroupsSeparated_AndImportsNotSeparated_ImportsSeparated() + { + var testCode = @" +using Microsoft.CodeAnalysis; +using System.Linq; +using System; + +class C +{ +}"; + + var expectedCode = @" +using Microsoft.CodeAnalysis; + +using System; +using System.Linq; + +class C +{ +}"; + + var editorConfig = new Dictionary() + { + ["end_of_line"] = EndOfLineFormatter.GetEndOfLineOption(Environment.NewLine), + ["dotnet_sort_system_directives_first"] = "false", + ["dotnet_separate_import_directive_groups"] = "true" + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig); + } + + [Fact] + public async Task WhenBothOptionsEnabled_AndImportsNotSortedOrSeparated_ImportsSortedAndSeparated() + { + var testCode = @" +using Microsoft.CodeAnalysis; +using System.Linq; +using System; + +class C +{ +}"; + + var expectedCode = @" +using System; +using System.Linq; + +using Microsoft.CodeAnalysis; + +class C +{ +}"; + + var editorConfig = new Dictionary() + { + ["end_of_line"] = EndOfLineFormatter.GetEndOfLineOption(Environment.NewLine), + ["dotnet_sort_system_directives_first"] = "true", + ["dotnet_separate_import_directive_groups"] = "true" + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig); + } + + [Fact] + public async Task WhenNeitherOptionIsConfigured_AndImportsNotSortedOrSeparated_NoChange() + { + var code = @" +using Microsoft.CodeAnalysis; +using System.Linq; +using System; + +class C +{ +}"; + + var editorConfig = new Dictionary() + { + ["end_of_line"] = EndOfLineFormatter.GetEndOfLineOption(Environment.NewLine) + }; + + await AssertCodeUnchangedAsync(code, editorConfig); + } + } +} diff --git a/test/dotnet-format.Tests/tests/Formatters/UnnecessaryImportsFormatterTests.cs b/test/dotnet-format.Tests/tests/Formatters/UnnecessaryImportsFormatterTests.cs new file mode 100644 index 000000000000..a97c1dd5b7fd --- /dev/null +++ b/test/dotnet-format.Tests/tests/Formatters/UnnecessaryImportsFormatterTests.cs @@ -0,0 +1,162 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Tools.Analyzers; +using Microsoft.CodeAnalysis.Tools.Formatters; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Formatters +{ + public class UnnecessaryImportsFormatterTests : CSharpFormatterTests + { + internal const string IDE0005 = nameof(IDE0005); + internal const string Style = nameof(Style); + + private const string RemoveUnnecessaryImportDiagnosticKey = + AnalyzerOptionsExtensions.DotnetDiagnosticPrefix + "." + IDE0005 + "." + AnalyzerOptionsExtensions.SeveritySuffix; + private const string RemoveUnnecessaryImportCategoryKey = + AnalyzerOptionsExtensions.DotnetAnalyzerDiagnosticPrefix + "." + AnalyzerOptionsExtensions.CategoryPrefix + "-" + Style + "." + AnalyzerOptionsExtensions.SeveritySuffix; + + private protected override ICodeFormatter Formatter => AnalyzerFormatter.CodeStyleFormatter; + + public UnnecessaryImportsFormatterTests(ITestOutputHelper output) + { + TestOutputHelper = output; + } + + [Fact] + public async Task WhenNotFixingCodeSyle_AndHasUnusedImports_NoChange() + { + var code = +@"using System; + +internal class C +{ +}"; + + var editorConfig = new Dictionary(); + + await AssertCodeUnchangedAsync(code, editorConfig, fixCategory: FixCategory.Whitespace, codeStyleSeverity: DiagnosticSeverity.Info); + } + + [Fact] + public async Task WhenIDE0005NotConfigured_AndHasUnusedImports_NoChange() + { + var code = +@"using System; + +internal class C +{ +}"; + + var editorConfig = new Dictionary(); + + await AssertCodeUnchangedAsync(code, editorConfig, fixCategory: FixCategory.Whitespace | FixCategory.CodeStyle, codeStyleSeverity: DiagnosticSeverity.Info); + } + + [Theory] + [InlineData(RemoveUnnecessaryImportDiagnosticKey, Severity.Warning)] + [InlineData(RemoveUnnecessaryImportDiagnosticKey, Severity.Info)] + [InlineData(RemoveUnnecessaryImportCategoryKey, Severity.Warning)] + [InlineData(RemoveUnnecessaryImportCategoryKey, Severity.Info)] + [InlineData(AnalyzerOptionsExtensions.DotnetAnalyzerDiagnosticSeverityKey, Severity.Warning)] + [InlineData(AnalyzerOptionsExtensions.DotnetAnalyzerDiagnosticSeverityKey, Severity.Info)] + public async Task WhenIDE0005SeverityLowerThanFixSeverity_AndHasUnusedImports_NoChange(string key, string severity) + { + var code = +@"using System; + +internal class C +{ +}"; + + var editorConfig = new Dictionary() + { + [key] = severity + }; + + await AssertCodeUnchangedAsync(code, editorConfig, fixCategory: FixCategory.Whitespace | FixCategory.CodeStyle, codeStyleSeverity: DiagnosticSeverity.Error); + } + + [Theory] + [InlineData(RemoveUnnecessaryImportDiagnosticKey, Severity.Warning)] + [InlineData(RemoveUnnecessaryImportDiagnosticKey, Severity.Error)] + [InlineData(RemoveUnnecessaryImportCategoryKey, Severity.Warning)] + [InlineData(RemoveUnnecessaryImportCategoryKey, Severity.Error)] + [InlineData(AnalyzerOptionsExtensions.DotnetAnalyzerDiagnosticSeverityKey, Severity.Warning)] + [InlineData(AnalyzerOptionsExtensions.DotnetAnalyzerDiagnosticSeverityKey, Severity.Error)] + public async Task WhenIDE0005SeverityEqualOrGreaterThanFixSeverity_AndHasUnusedImports_ImportRemoved(string key, string severity) + { + var testCode = +@"using System; + +internal class C +{ +}"; + + var expectedCode = +@"internal class C +{ +}"; + + var editorConfig = new Dictionary() + { + [key] = severity + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig, fixCategory: FixCategory.Whitespace | FixCategory.CodeStyle, codeStyleSeverity: DiagnosticSeverity.Warning); + } + + [Theory] + [InlineData(RemoveUnnecessaryImportDiagnosticKey, Severity.Warning)] + [InlineData(RemoveUnnecessaryImportDiagnosticKey, Severity.Error)] + [InlineData(RemoveUnnecessaryImportCategoryKey, Severity.Warning)] + [InlineData(RemoveUnnecessaryImportCategoryKey, Severity.Error)] + [InlineData(AnalyzerOptionsExtensions.DotnetAnalyzerDiagnosticSeverityKey, Severity.Warning)] + [InlineData(AnalyzerOptionsExtensions.DotnetAnalyzerDiagnosticSeverityKey, Severity.Error)] + public async Task WhenIDE0005SeverityEqualOrGreaterThanFixSeverity_AndHasUnusedImports_AndIncludedInDiagnosticsList_ImportRemoved(string key, string severity) + { + var testCode = +@"using System; + +internal class C +{ +}"; + + var expectedCode = +@"internal class C +{ +}"; + + var editorConfig = new Dictionary() + { + [key] = severity + }; + + await AssertCodeChangedAsync(testCode, expectedCode, editorConfig, fixCategory: FixCategory.Whitespace | FixCategory.CodeStyle, codeStyleSeverity: DiagnosticSeverity.Warning, diagnostics: new[] { IDE0005 }); + } + + [Theory] + [InlineData(RemoveUnnecessaryImportDiagnosticKey, Severity.Warning)] + [InlineData(RemoveUnnecessaryImportDiagnosticKey, Severity.Error)] + [InlineData(RemoveUnnecessaryImportCategoryKey, Severity.Warning)] + [InlineData(RemoveUnnecessaryImportCategoryKey, Severity.Error)] + [InlineData(AnalyzerOptionsExtensions.DotnetAnalyzerDiagnosticSeverityKey, Severity.Warning)] + [InlineData(AnalyzerOptionsExtensions.DotnetAnalyzerDiagnosticSeverityKey, Severity.Error)] + public async Task WhenIDE0005SeverityEqualOrGreaterThanFixSeverity_AndHasUnusedImports_AndNotIncludedInDiagnosticsList_ImportNotRemoved(string key, string severity) + { + var testCode = +@"using System; + +internal class C +{ +}"; + + var editorConfig = new Dictionary() + { + [key] = severity + }; + + await AssertCodeUnchangedAsync(testCode, editorConfig, fixCategory: FixCategory.Whitespace | FixCategory.CodeStyle, codeStyleSeverity: DiagnosticSeverity.Warning, diagnostics: new[] { "IDE0073" }); + } + } +} diff --git a/test/dotnet-format.Tests/tests/MSBuild/MSBuildWorkspaceFinderTests.cs b/test/dotnet-format.Tests/tests/MSBuild/MSBuildWorkspaceFinderTests.cs new file mode 100644 index 000000000000..28526b8c95e4 --- /dev/null +++ b/test/dotnet-format.Tests/tests/MSBuild/MSBuildWorkspaceFinderTests.cs @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Tools.Tests.Utilities; +using Microsoft.CodeAnalysis.Tools.Workspaces; + +namespace Microsoft.CodeAnalysis.Tools.Tests.MSBuild +{ + public class MSBuildWorkspaceFinderTests + { + private string ProjectsPath => TestProjectsPathHelper.GetProjectsDirectory(); + + [Fact] + public void ThrowsException_CannotFindMSBuildProjectFile() + { + var workspacePath = "for_workspace_finder/no_project_or_solution/"; + var exceptionMessageStart = string.Format( + Resources.Could_not_find_a_MSBuild_project_or_solution_file_in_0_Specify_which_to_use_with_the_workspace_argument, + Path.Combine(ProjectsPath, workspacePath)).Replace('/', Path.DirectorySeparatorChar); + var exception = Assert.Throws(() => MSBuildWorkspaceFinder.FindWorkspace(ProjectsPath, workspacePath)); + Assert.StartsWith(exceptionMessageStart, exception.Message); + } + + [Fact] + public void ThrowsException_MultipleMSBuildProjectFiles() + { + var workspacePath = "for_workspace_finder/multiple_projects/"; + var exceptionMessageStart = string.Format( + Resources.Multiple_MSBuild_project_files_found_in_0_Specify_which_to_use_with_the_workspace_argument, + Path.Combine(ProjectsPath, workspacePath)).Replace('/', Path.DirectorySeparatorChar); + var exception = Assert.Throws(() => MSBuildWorkspaceFinder.FindWorkspace(ProjectsPath, workspacePath)); + Assert.Equal(exceptionMessageStart, exception.Message); + } + + [Fact] + public void ThrowsException_MultipleMSBuildSolutionFiles() + { + var workspacePath = "for_workspace_finder/multiple_solutions/"; + var exceptionMessageStart = string.Format( + Resources.Multiple_MSBuild_solution_files_found_in_0_Specify_which_to_use_with_the_workspace_argument, + Path.Combine(ProjectsPath, workspacePath)).Replace('/', Path.DirectorySeparatorChar); + var exception = Assert.Throws(() => MSBuildWorkspaceFinder.FindWorkspace(ProjectsPath, workspacePath)); + Assert.Equal(exceptionMessageStart, exception.Message); + } + + [Fact] + public void ThrowsException_SolutionAndProjectAmbiguity() + { + var workspacePath = "for_workspace_finder/project_and_solution/"; + var exceptionMessageStart = string.Format( + Resources.Both_a_MSBuild_project_file_and_solution_file_found_in_0_Specify_which_to_use_with_the_workspace_argument, + Path.Combine(ProjectsPath, workspacePath)).Replace('/', Path.DirectorySeparatorChar); + var exception = Assert.Throws(() => MSBuildWorkspaceFinder.FindWorkspace(ProjectsPath, workspacePath)); + Assert.Equal(exceptionMessageStart, exception.Message); + } + + [Fact] + public void FindsSolutionByFolder() + { + const string Path = "for_workspace_finder/single_solution/"; + + var (isSolution, workspacePath) = MSBuildWorkspaceFinder.FindWorkspace(ProjectsPath, Path); + + var solutionFileName = System.IO.Path.GetFileName(workspacePath); + Assert.Equal("single_solution.sln", solutionFileName); + Assert.True(isSolution); + } + + [Fact] + public void FindsSolutionByFilePath() + { + const string Path = "for_workspace_finder/multiple_solutions/solution_b.sln"; + + var (isSolution, workspacePath) = MSBuildWorkspaceFinder.FindWorkspace(ProjectsPath, Path); + + var solutionFileName = System.IO.Path.GetFileName(workspacePath); + Assert.Equal("solution_b.sln", solutionFileName); + Assert.True(isSolution); + } + + [Fact] + public void FindsProjectByFolder() + { + const string Path = "for_workspace_finder/single_project/"; + + var (isSolution, workspacePath) = MSBuildWorkspaceFinder.FindWorkspace(ProjectsPath, Path); + + var solutionFileName = System.IO.Path.GetFileName(workspacePath); + Assert.Equal("single_project.csproj", solutionFileName); + Assert.False(isSolution); + } + + [Fact] + public void FindsProjectByFilePath() + { + const string Path = "for_workspace_finder/multiple_projects/project_b.csproj"; + + var (isSolution, workspacePath) = MSBuildWorkspaceFinder.FindWorkspace(ProjectsPath, Path); + + var solutionFileName = System.IO.Path.GetFileName(workspacePath); + Assert.Equal("project_b.csproj", solutionFileName); + Assert.False(isSolution); + } + } +} diff --git a/test/dotnet-format.Tests/tests/MSBuild/MSBuildWorkspaceLoaderTests.cs b/test/dotnet-format.Tests/tests/MSBuild/MSBuildWorkspaceLoaderTests.cs new file mode 100644 index 000000000000..92f5ff4e1557 --- /dev/null +++ b/test/dotnet-format.Tests/tests/MSBuild/MSBuildWorkspaceLoaderTests.cs @@ -0,0 +1,178 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.MSBuild; +using Microsoft.CodeAnalysis.Tools.Tests.Utilities; +using Microsoft.CodeAnalysis.Tools.Tests.XUnit; +using Microsoft.CodeAnalysis.Tools.Workspaces; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Tests.MSBuild +{ + public class MSBuildWorkspaceLoaderTests + { + // Microsoft.CodeAnalysis.CSharp.ErrorCode + private const string ERR_NameNotInContext = "CS0103"; + private const string ERR_NoEntryPoint = "CS5001"; + + // Microsoft.CodeAnalysis.VisualBasic.ERRID + private const string ERR_UndefinedType1 = "BC30002"; + + private static string ProjectsPath => TestProjectsPathHelper.GetProjectsDirectory(); + + protected ITestOutputHelper TestOutputHelper { get; set; } + + public MSBuildWorkspaceLoaderTests(ITestOutputHelper output) + { + TestOutputHelper = output; + } + + [MSBuildTheory(typeof(WindowsOnly))] + [InlineData("winforms")] + [InlineData("winformslib")] + [InlineData("wpf")] + [InlineData("wpfusercontrollib")] + [InlineData("wpflib")] + [InlineData("wpfcustomcontrollib")] + public async Task CSharpTemplateProject_WindowsOnly_LoadWithNoDiagnostics(string templateName) + { + var ignoredDiagnostics = templateName switch + { + "wpf" => new string[] { ERR_NoEntryPoint, ERR_NameNotInContext }, + "wpfusercontrollib" => new string[] { ERR_NameNotInContext }, + _ => Array.Empty(), + }; + + await AssertTemplateProjectLoadsCleanlyAsync(templateName, LanguageNames.CSharp, ignoredDiagnostics); + } + + [MSBuildTheory] + [InlineData("web")] + [InlineData("grpc")] + [InlineData("webapi")] + [InlineData("razor")] + [InlineData("mvc")] + [InlineData("blazor")] + [InlineData("blazorwasm")] + [InlineData("classlib")] + [InlineData("console")] + [InlineData("mstest")] + [InlineData("nunit")] + [InlineData("razorclasslib")] + [InlineData("worker")] + [InlineData("xunit")] + public async Task CSharpTemplateProject_LoadWithNoDiagnostics(string templateName) + { + var ignoredDiagnostics = templateName switch + { + _ => Array.Empty(), + }; + + await AssertTemplateProjectLoadsCleanlyAsync(templateName, LanguageNames.CSharp, ignoredDiagnostics); + } + + [MSBuildTheory] + [InlineData("classlib")] + [InlineData("console")] + [InlineData("mstest")] + [InlineData("nunit")] + [InlineData("xunit")] + public async Task VisualBasicTemplateProject_LoadWithNoDiagnostics(string templateName) + { + var ignoredDiagnostics = (templateName, isWindows: OperatingSystem.IsWindows()) switch + { + (_, isWindows: false) => new string[] { ERR_UndefinedType1 }, + _ => Array.Empty(), + }; + + await AssertTemplateProjectLoadsCleanlyAsync(templateName, LanguageNames.VisualBasic, ignoredDiagnostics); + } + + private async Task AssertTemplateProjectLoadsCleanlyAsync(string templateName, string languageName, string[] ignoredDiagnostics = null) + { + var logger = new TestLogger(); + + try + { + if (ignoredDiagnostics is not null) + { + TestOutputHelper.WriteLine($"Ignoring compiler diagnostics: \"{string.Join("\", \"", ignoredDiagnostics)}\""); + } + + // Clean up previous run + CleanupProject(templateName, languageName); + + var projectFilePath = await GenerateProjectFromTemplateAsync(templateName, languageName, TestOutputHelper); + + await AssertProjectLoadsCleanlyAsync(projectFilePath, logger, ignoredDiagnostics); + + // Clean up successful run + CleanupProject(templateName, languageName); + } + catch + { + TestOutputHelper.WriteLine(logger.GetLog()); + throw; + } + } + + private static async Task GenerateProjectFromTemplateAsync(string templateName, string languageName, ITestOutputHelper outputHelper) + { + var projectPath = GetProjectPath(templateName, languageName); + var projectFilePath = GetProjectFilePath(projectPath, languageName); + + var exitCode = await DotNetHelper.NewProjectAsync(templateName, projectPath, languageName, outputHelper); + Assert.Equal(0, exitCode); + + return projectFilePath; + } + + private static async Task AssertProjectLoadsCleanlyAsync(string projectFilePath, ILogger logger, string[] ignoredDiagnostics) + { + var binaryLogPath = Path.ChangeExtension(projectFilePath, ".binlog"); + + MSBuildRegistrar.RegisterInstance(); + using var workspace = (MSBuildWorkspace)await MSBuildWorkspaceLoader.LoadAsync(projectFilePath, WorkspaceType.Project, binaryLogPath, logWorkspaceWarnings: true, logger, CancellationToken.None); + + Assert.Empty(workspace.Diagnostics); + + var project = workspace.CurrentSolution.Projects.Single(); + var compilation = await project.GetCompilationAsync(); + + // Unnecessary using directives are reported with a severty of Hidden + var diagnostics = compilation.GetDiagnostics() + .Where(diagnostic => diagnostic.Severity > DiagnosticSeverity.Hidden && ignoredDiagnostics?.Contains(diagnostic.Id) != true); + + Assert.Empty(diagnostics); + } + + private static void CleanupProject(string templateName, string languageName) + { + var projectPath = GetProjectPath(templateName, languageName); + + if (Directory.Exists(projectPath)) + { + Directory.Delete(projectPath, true); + } + } + + private static string GetProjectPath(string templateName, string languageName) + { + var languagePrefix = languageName.Replace("#", "Sharp").Replace(' ', '_').ToLower(); + var projectName = $"{languagePrefix}_{templateName}_project"; + return Path.Combine(ProjectsPath, "for_workspace_loader", projectName); + } + + private static string GetProjectFilePath(string projectPath, string languageName) + { + var projectName = Path.GetFileName(projectPath); + var projectExtension = languageName switch + { + LanguageNames.CSharp => "csproj", + LanguageNames.VisualBasic => "vbproj", + _ => throw new ArgumentOutOfRangeException(nameof(languageName), actualValue: languageName, message: "Only C# and VB.Net project are supported.") + }; + return Path.Combine(projectPath, $"{projectName}.{projectExtension}"); + } + } +} diff --git a/test/dotnet-format.Tests/tests/ProgramTests.cs b/test/dotnet-format.Tests/tests/ProgramTests.cs new file mode 100644 index 000000000000..469c73af7aa7 --- /dev/null +++ b/test/dotnet-format.Tests/tests/ProgramTests.cs @@ -0,0 +1,247 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Tools.Commands; + +namespace Microsoft.CodeAnalysis.Tools.Tests +{ + public class ProgramTests + { + [Fact] + public void ExitCodeIsOneWithCheckAndAnyFilesFormatted() + { + var formatResult = new WorkspaceFormatResult(filesFormatted: 1, fileCount: 0, exitCode: 0); + var exitCode = FormatCommandCommon.GetExitCode(formatResult, check: true); + + Assert.Equal(FormatCommandCommon.CheckFailedExitCode, exitCode); + } + + [Fact] + public void ExitCodeIsZeroWithCheckAndNoFilesFormatted() + { + var formatResult = new WorkspaceFormatResult(filesFormatted: 0, fileCount: 0, exitCode: 42); + var exitCode = FormatCommandCommon.GetExitCode(formatResult, check: true); + + Assert.Equal(0, exitCode); + } + + [Fact] + public void ExitCodeIsSameWithoutCheck() + { + var formatResult = new WorkspaceFormatResult(filesFormatted: 0, fileCount: 0, exitCode: 42); + var exitCode = FormatCommandCommon.GetExitCode(formatResult, check: false); + + Assert.Equal(formatResult.ExitCode, exitCode); + } + + [Fact] + public void CommandLine_OptionsAreParsedCorrectly() + { + // Arrange + var sut = RootFormatCommand.GetCommand(); + + // Act + var result = sut.Parse(new[] { + "--no-restore", + "--include", "include1", "include2", + "--exclude", "exclude1", "exclude2", + "--verify-no-changes", + "--binarylog", "binary-log-path", + "--report", "report", + "--verbosity", "detailed", + "--include-generated"}); + + // Assert + Assert.Empty(result.Errors); + Assert.Empty(result.UnmatchedTokens); + Assert.Empty(result.UnmatchedTokens); + result.GetValue(FormatCommandCommon.NoRestoreOption); + Assert.Collection(result.GetValue(FormatCommandCommon.IncludeOption), + i0 => Assert.Equal("include1", i0), + i1 => Assert.Equal("include2", i1)); + Assert.Collection(result.GetValue(FormatCommandCommon.ExcludeOption), + i0 => Assert.Equal("exclude1", i0), + i1 => Assert.Equal("exclude2", i1)); + Assert.True(result.GetValue(FormatCommandCommon.VerifyNoChanges)); + Assert.Equal("binary-log-path", result.GetValue(FormatCommandCommon.BinarylogOption)); + Assert.Equal("report", result.GetValue(FormatCommandCommon.ReportOption)); + Assert.Equal("detailed", result.GetValue(FormatCommandCommon.VerbosityOption)); + Assert.True(result.GetValue(FormatCommandCommon.IncludeGeneratedOption)); + } + + [Fact] + public void CommandLine_ProjectArgument_Simple() + { + // Arrange + var sut = RootFormatCommand.GetCommand(); + + // Act + var result = sut.Parse(new[] { "workspaceValue" }); + + // Assert + Assert.Empty(result.Errors); + Assert.Equal("workspaceValue", result.GetValue(FormatCommandCommon.SlnOrProjectArgument)); + } + + [Fact] + public void CommandLine_ProjectArgument_WithOption_AfterArgument() + { + // Arrange + var sut = RootFormatCommand.GetCommand(); + + // Act + var result = sut.Parse(new[] { "workspaceValue", "--verbosity", "detailed" }); + + // Assert + Assert.Empty(result.Errors); + Assert.Equal("workspaceValue", result.GetValue(FormatCommandCommon.SlnOrProjectArgument)); + Assert.Equal("detailed", result.GetValue(FormatCommandCommon.VerbosityOption)); + } + + [Fact] + public void CommandLine_ProjectArgument_WithOption_BeforeArgument() + { + // Arrange + var sut = RootFormatCommand.GetCommand(); + + // Act + var result = sut.Parse(new[] { "--verbosity", "detailed", "workspaceValue" }); + + // Assert + Assert.Empty(result.Errors); + Assert.Equal("workspaceValue", result.GetValue(FormatCommandCommon.SlnOrProjectArgument)); + Assert.Equal("detailed", result.GetValue(FormatCommandCommon.VerbosityOption)); + } + + [Fact] + public void CommandLine_ProjectArgument_FailsIfSpecifiedTwice() + { + // Arrange + var sut = RootFormatCommand.GetCommand(); + + // Act + var result = sut.Parse(new[] { "workspaceValue1", "workspaceValue2" }); + + // Assert + Assert.Single(result.Errors); + } + + [Fact] + public void CommandLine_FolderValidation_FailsIfFixAnalyzersSpecified() + { + // Arrange + var sut = RootFormatCommand.GetCommand(); + + // Act + var result = sut.Parse(new[] { "--folder", "--fix-analyzers" }); + + // Assert + Assert.Single(result.Errors); + } + + [Fact] + public void CommandLine_FolderValidation_FailsIfFixStyleSpecified() + { + // Arrange + var sut = RootFormatCommand.GetCommand(); + + // Act + var result = sut.Parse(new[] { "--folder", "--fix-style" }); + + // Assert + Assert.Single(result.Errors); + } + + [Fact] + public void CommandLine_FolderValidation_FailsIfNoRestoreSpecified() + { + // Arrange + var sut = RootFormatCommand.GetCommand(); + + // Act + var result = sut.Parse(new[] { "whitespace", "--folder", "--no-restore" }); + + // Assert + Assert.Single(result.Errors); + } + + [Fact] + public void CommandLine_BinaryLog_DoesNotFailIfPathNotSpecified() + { + // Arrange + var sut = RootFormatCommand.GetCommand(); + + // Act + var result = sut.Parse(new[] { "--binarylog" }); + + // Assert + Assert.Empty(result.Errors); + Assert.NotNull(result.GetResult(FormatCommandCommon.BinarylogOption)); + } + + [Fact] + public void CommandLine_BinaryLog_DoesNotFailIfPathIsSpecified() + { + // Arrange + var sut = RootFormatCommand.GetCommand(); + + // Act + var result = sut.Parse(new[] { "--binarylog", "log" }); + + // Assert + Assert.Empty(result.Errors); + Assert.NotNull(result.GetResult(FormatCommandCommon.BinarylogOption)); + } + + [Fact] + public void CommandLine_BinaryLog_FailsIfFolderIsSpecified() + { + // Arrange + var sut = RootFormatCommand.GetCommand(); + + // Act + var result = sut.Parse(new[] { "whitespace", "--folder", "--binarylog" }); + + // Assert + Assert.Single(result.Errors); + } + + [Fact] + public void CommandLine_Diagnostics_FailsIfDiagnosticNoSpecified() + { + // Arrange + var sut = RootFormatCommand.GetCommand(); + + // Act + var result = sut.Parse(new[] { "--diagnostics" }); + + // Assert + Assert.Single(result.Errors); + } + + [Fact] + public void CommandLine_Diagnostics_DoesNotFailIfDiagnosticIsSpecified() + { + // Arrange + var sut = RootFormatCommand.GetCommand(); + + // Act + var result = sut.Parse(new[] { "--diagnostics", "RS0016" }); + + // Assert + Assert.Empty(result.Errors); + } + + [Fact] + public void CommandLine_Diagnostics_DoesNotFailIfMultipleDiagnosticAreSpecified() + { + // Arrange + var sut = RootFormatCommand.GetCommand(); + + // Act + var result = sut.Parse(new[] { "--diagnostics", "RS0016", "RS0017", "RS0018" }); + + // Assert + Assert.Empty(result.Errors); + } + } +} diff --git a/test/dotnet-format.Tests/tests/Utilities/DotNetHelper.cs b/test/dotnet-format.Tests/tests/Utilities/DotNetHelper.cs new file mode 100644 index 000000000000..62aeac1787d1 --- /dev/null +++ b/test/dotnet-format.Tests/tests/Utilities/DotNetHelper.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Tools.Utilities; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Utilities +{ + internal static partial class DotNetHelper + { + public static async Task NewProjectAsync(string templateName, string outputPath, string languageName, ITestOutputHelper output) + { + var language = languageName switch + { + LanguageNames.CSharp => "C#", + LanguageNames.VisualBasic => "VB", + LanguageNames.FSharp => "F#", + _ => throw new ArgumentOutOfRangeException(nameof(languageName), actualValue: languageName, message: "Only C#, F# and VB.NET project are supported.") + }; + + var processInfo = ProcessRunner.CreateProcess("dotnet", $"new \"{templateName}\" -o \"{outputPath}\" --language \"{language}\"", captureOutput: true, displayWindow: false); + var restoreResult = await processInfo.Result; + + output.WriteLine(string.Join(Environment.NewLine, restoreResult.OutputLines)); + + return restoreResult.ExitCode; + } + + public static async Task PerformBuildAsync(string workspaceFilePath, ITestOutputHelper output) + { + var workspacePath = Path.IsPathRooted(workspaceFilePath) + ? workspaceFilePath + : Path.Combine(TestProjectsPathHelper.GetProjectsDirectory(), workspaceFilePath); + + var processInfo = ProcessRunner.CreateProcess("dotnet", $"build \"{workspacePath}\"", captureOutput: true, displayWindow: false); + var restoreResult = await processInfo.Result; + + output.WriteLine(string.Join(Environment.NewLine, restoreResult.OutputLines)); + + return restoreResult.ExitCode; + } + + public static async Task PerformRestoreAsync(string workspaceFilePath, ITestOutputHelper output) + { + var workspacePath = Path.IsPathRooted(workspaceFilePath) + ? workspaceFilePath + : Path.Combine(TestProjectsPathHelper.GetProjectsDirectory(), workspaceFilePath); + + var processInfo = ProcessRunner.CreateProcess("dotnet", $"restore \"{workspacePath}\"", captureOutput: true, displayWindow: false); + var restoreResult = await processInfo.Result; + + output.WriteLine(string.Join(Environment.NewLine, restoreResult.OutputLines)); + + return restoreResult.ExitCode; + } + } +} diff --git a/test/dotnet-format.Tests/tests/Utilities/MSBuildRegistrar.cs b/test/dotnet-format.Tests/tests/Utilities/MSBuildRegistrar.cs new file mode 100644 index 000000000000..6a712375721d --- /dev/null +++ b/test/dotnet-format.Tests/tests/Utilities/MSBuildRegistrar.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +#nullable enable + +namespace Microsoft.CodeAnalysis.Tools.Tests.Utilities +{ + /// + /// Loads MSBuild assemblies. + /// + public sealed class MSBuildRegistrar + { + private static int s_registered; + + private static string? s_msBuildPath; + + public static string RegisterInstance() + { + if (Interlocked.Exchange(ref s_registered, 1) == 0) + { + var msBuildInstance = Build.Locator.MSBuildLocator.QueryVisualStudioInstances().First(); + s_msBuildPath = Path.EndsInDirectorySeparator(msBuildInstance.MSBuildPath) + ? msBuildInstance.MSBuildPath + : msBuildInstance.MSBuildPath + Path.DirectorySeparatorChar; + Build.Locator.MSBuildLocator.RegisterMSBuildPath(s_msBuildPath); + } + + return s_msBuildPath!; + } + } +} diff --git a/test/dotnet-format.Tests/tests/Utilities/Severity.cs b/test/dotnet-format.Tests/tests/Utilities/Severity.cs new file mode 100644 index 000000000000..19c7042a0c9b --- /dev/null +++ b/test/dotnet-format.Tests/tests/Utilities/Severity.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Tools.Tests +{ + internal static class Severity + { + public const string Info = "info"; + public const string Warning = "warning"; + public const string Error = "error"; + } +} diff --git a/test/dotnet-format.Tests/tests/Utilities/TestLogger.cs b/test/dotnet-format.Tests/tests/Utilities/TestLogger.cs new file mode 100644 index 000000000000..6ebc283215ba --- /dev/null +++ b/test/dotnet-format.Tests/tests/Utilities/TestLogger.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Tools.Logging; +using Microsoft.Extensions.Logging; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Utilities +{ + /// + /// Logger that records all logged messages. + /// + internal class TestLogger : ILogger + { + private readonly StringBuilder _builder = new StringBuilder(); + + public IDisposable BeginScope(TState state) + { + return NullScope.Instance; + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + var message = formatter(state, exception); + _builder.AppendLine(message); + } + + public string GetLog() + { + return _builder.ToString(); + } + } +} diff --git a/test/dotnet-format.Tests/tests/Utilities/TestProjectsPathHelper.cs b/test/dotnet-format.Tests/tests/Utilities/TestProjectsPathHelper.cs new file mode 100644 index 000000000000..cfb8b8019c3e --- /dev/null +++ b/test/dotnet-format.Tests/tests/Utilities/TestProjectsPathHelper.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Tools.Tests.Utilities +{ + public static class TestProjectsPathHelper + { + private static string s_projectsDirectory; + + public static string GetProjectsDirectory() + { + if (s_projectsDirectory == null) + { + var assetsDirectory = Path.Combine(TestContext.Current.TestAssetsDirectory, "dotnet-format.TestsProjects"); + if (Directory.Exists(assetsDirectory)) + { + s_projectsDirectory = assetsDirectory; + return assetsDirectory; + } + + throw new ArgumentException("Can't find the project asserts directory"); + } + + return s_projectsDirectory; + } + } +} diff --git a/test/dotnet-format.Tests/tests/Utilities/XmlReferenceResolver.cs b/test/dotnet-format.Tests/tests/Utilities/XmlReferenceResolver.cs new file mode 100644 index 000000000000..b61e9585edfd --- /dev/null +++ b/test/dotnet-format.Tests/tests/Utilities/XmlReferenceResolver.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Runtime.CompilerServices; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Utilities +{ + internal class TestXmlReferenceResolver : XmlReferenceResolver + { + public Dictionary XmlReferences { get; } = + new Dictionary(); + + public override bool Equals(object other) + { + return ReferenceEquals(this, other); + } + + public override int GetHashCode() + { +#pragma warning disable RS1024 // Compare symbols correctly + return RuntimeHelpers.GetHashCode(this); +#pragma warning restore RS1024 // Compare symbols correctly + } + + public override Stream OpenRead(string resolvedPath) + { + if (!XmlReferences.TryGetValue(resolvedPath, out var content)) + { + return null; + } + + return new MemoryStream(Encoding.UTF8.GetBytes(content)); + } + + public override string ResolveReference(string path, string baseFilePath) + { + return path; + } + } +} diff --git a/test/dotnet-format.Tests/tests/XUnit/ConditionalFactAttribute.cs b/test/dotnet-format.Tests/tests/XUnit/ConditionalFactAttribute.cs new file mode 100644 index 000000000000..8a3ea400bad4 --- /dev/null +++ b/test/dotnet-format.Tests/tests/XUnit/ConditionalFactAttribute.cs @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +#nullable enable + +namespace Microsoft.CodeAnalysis.Tools.Tests.XUnit +{ + public class ConditionalFactAttribute : FactAttribute + { + /// + /// This property exists to prevent users of ConditionalFact from accidentally putting documentation + /// in the Skip property instead of Reason. Putting it into Skip would cause the test to be unconditionally + /// skipped vs. conditionally skipped which is the entire point of this attribute. + /// + [Obsolete("ConditionalFact should use Reason or AlwaysSkip", error: true)] + public new string Skip + { + get => base.Skip; + set => base.Skip = value; + } + + /// + /// Used to unconditionally Skip a test. For the rare occasion when a conditional test needs to be + /// unconditionally skipped (typically short term for a bug to be fixed). + /// + public string AlwaysSkip + { + get => base.Skip; + set => base.Skip = value; + } + + public string? Reason { get; set; } + + public ConditionalFactAttribute(params Type[] skipConditions) + { + foreach (var skipCondition in skipConditions) + { + var condition = (ExecutionCondition?)Activator.CreateInstance(skipCondition); + if (condition?.ShouldSkip == true) + { + base.Skip = Reason ?? condition.SkipReason; + break; + } + } + } + } + + public class ConditionalTheoryAttribute : TheoryAttribute + { + /// + /// This property exists to prevent users of ConditionalFact from accidentally putting documentation + /// in the Skip property instead of Reason. Putting it into Skip would cause the test to be unconditionally + /// skipped vs. conditionally skipped which is the entire point of this attribute. + /// + [Obsolete("ConditionalTheory should use Reason or AlwaysSkip")] + public new string Skip + { + get => base.Skip; + set => base.Skip = value; + } + + /// + /// Used to unconditionally Skip a test. For the rare occasion when a conditional test needs to be + /// unconditionally skipped (typically short term for a bug to be fixed). + /// + public string AlwaysSkip + { + get => base.Skip; + set => base.Skip = value; + } + + public string? Reason { get; set; } + + public ConditionalTheoryAttribute(params Type[] skipConditions) + { + foreach (var skipCondition in skipConditions) + { + var condition = (ExecutionCondition?)Activator.CreateInstance(skipCondition); + if (condition?.ShouldSkip == true) + { + base.Skip = Reason ?? condition.SkipReason; + break; + } + } + } + } + + public abstract class ExecutionCondition + { + public abstract bool ShouldSkip { get; } + public abstract string SkipReason { get; } + } + + public static class ExecutionConditionUtil + { + public static bool IsWindows => Path.DirectorySeparatorChar == '\\'; + public static bool IsMacOS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + public static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + } + + public class WindowsOnly : ExecutionCondition + { + public override bool ShouldSkip => !ExecutionConditionUtil.IsWindows; + public override string SkipReason => "Test not supported on Mac and Linux"; + } +} diff --git a/test/dotnet-format.Tests/tests/XUnit/MSBuildFactAttribute.cs b/test/dotnet-format.Tests/tests/XUnit/MSBuildFactAttribute.cs new file mode 100644 index 000000000000..36fc8920cad3 --- /dev/null +++ b/test/dotnet-format.Tests/tests/XUnit/MSBuildFactAttribute.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Xunit.Sdk; + +#nullable enable + +namespace Microsoft.CodeAnalysis.Tools.Tests.XUnit +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + [XunitTestCaseDiscoverer("Microsoft.CodeAnalysis.Tools.Tests.XUnit.MSBuildFactDiscoverer", "dotnet-format.UnitTests")] + public sealed class MSBuildFactAttribute : ConditionalFactAttribute + { + public MSBuildFactAttribute(params Type[] skipConditions) + : base(skipConditions) + { + } + } +} diff --git a/test/dotnet-format.Tests/tests/XUnit/MSBuildFactDiscoverer.cs b/test/dotnet-format.Tests/tests/XUnit/MSBuildFactDiscoverer.cs new file mode 100644 index 000000000000..71e24be2c171 --- /dev/null +++ b/test/dotnet-format.Tests/tests/XUnit/MSBuildFactDiscoverer.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Xunit.Sdk; + +#nullable enable + +namespace Microsoft.CodeAnalysis.Tools.Tests.XUnit +{ + + public sealed class MSBuildFactDiscoverer : IXunitTestCaseDiscoverer + { + private readonly FactDiscoverer _factDiscoverer; + + public MSBuildFactDiscoverer(IMessageSink diagnosticMessageSink) + { + _factDiscoverer = new FactDiscoverer(diagnosticMessageSink); + } + + public IEnumerable Discover( + ITestFrameworkDiscoveryOptions discoveryOptions, + ITestMethod testMethod, + IAttributeInfo factAttribute) + { + return _factDiscoverer + .Discover(discoveryOptions, testMethod, factAttribute) + .Select(testCase => new MSBuildTestCase(testCase)); + } + } +} diff --git a/test/dotnet-format.Tests/tests/XUnit/MSBuildTestCase.cs b/test/dotnet-format.Tests/tests/XUnit/MSBuildTestCase.cs new file mode 100644 index 000000000000..172d2bc1f55a --- /dev/null +++ b/test/dotnet-format.Tests/tests/XUnit/MSBuildTestCase.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Diagnostics; +using Microsoft.CodeAnalysis.Tools.Workspaces; +using Xunit.Sdk; + +namespace Microsoft.CodeAnalysis.Tools.Tests.XUnit +{ + [DebuggerDisplay(@"\{ class = {TestMethod.TestClass.Class.Name}, method = {TestMethod.Method.Name}, display = {DisplayName}, skip = {SkipReason} \}")] + public sealed class MSBuildTestCase : Xunit.LongLivedMarshalByRefObject, IXunitTestCase + { + private IXunitTestCase _testCase; + + public string DisplayName => _testCase.DisplayName; + public IMethodInfo Method => _testCase.Method; + public string SkipReason => _testCase.SkipReason; + public ITestMethod TestMethod => _testCase.TestMethod; + public object[] TestMethodArguments => _testCase.TestMethodArguments; + public Dictionary> Traits => _testCase.Traits; + public string UniqueID => _testCase.UniqueID; + + public ISourceInformation SourceInformation + { + get => _testCase.SourceInformation; + set => _testCase.SourceInformation = value; + } + + public Exception InitializationException => _testCase.InitializationException; + + public int Timeout => _testCase.Timeout; + + public MSBuildTestCase(IXunitTestCase testCase) + { + _testCase = testCase ?? throw new ArgumentNullException(nameof(testCase)); + } + + [Obsolete("Called by the deserializer", error: true)] + public MSBuildTestCase() { } + + public async Task RunAsync( + IMessageSink diagnosticMessageSink, + IMessageBus messageBus, + object[] constructorArguments, + ExceptionAggregator aggregator, + CancellationTokenSource cancellationTokenSource) + { + await MSBuildWorkspaceLoader.Guard.WaitAsync(); + try + { + var runner = new XunitTestCaseRunner(this, DisplayName, SkipReason, constructorArguments, TestMethodArguments, messageBus, aggregator, cancellationTokenSource); + return await runner.RunAsync(); + } + finally + { + MSBuildWorkspaceLoader.Guard.Release(); + } + } + + public void Deserialize(IXunitSerializationInfo info) + { + _testCase = info.GetValue("InnerTestCase"); + } + + public void Serialize(IXunitSerializationInfo info) + { + info.AddValue("InnerTestCase", _testCase); + } + } +} diff --git a/test/dotnet-format.Tests/tests/XUnit/MSBuildTheoryAttribute.cs b/test/dotnet-format.Tests/tests/XUnit/MSBuildTheoryAttribute.cs new file mode 100644 index 000000000000..c532956c5a99 --- /dev/null +++ b/test/dotnet-format.Tests/tests/XUnit/MSBuildTheoryAttribute.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Xunit.Sdk; + +#nullable enable + +namespace Microsoft.CodeAnalysis.Tools.Tests.XUnit +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + [XunitTestCaseDiscoverer("Microsoft.CodeAnalysis.Tools.Tests.XUnit.MSBuildTheoryDiscoverer", "dotnet-format.UnitTests")] + public sealed class MSBuildTheoryAttribute : ConditionalTheoryAttribute + { + public MSBuildTheoryAttribute(params Type[] skipConditions) + : base(skipConditions) + { + } + } +} diff --git a/test/dotnet-format.Tests/tests/XUnit/MSBuildTheoryDiscoverer.cs b/test/dotnet-format.Tests/tests/XUnit/MSBuildTheoryDiscoverer.cs new file mode 100644 index 000000000000..520df0a915e6 --- /dev/null +++ b/test/dotnet-format.Tests/tests/XUnit/MSBuildTheoryDiscoverer.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using Xunit.Sdk; + +#nullable enable + +namespace Microsoft.CodeAnalysis.Tools.Tests.XUnit +{ + + public sealed class MSBuildTheoryDiscoverer : IXunitTestCaseDiscoverer + { + private readonly TheoryDiscoverer _theoryDiscoverer; + + public MSBuildTheoryDiscoverer(IMessageSink diagnosticMessageSink) + { + _theoryDiscoverer = new TheoryDiscoverer(diagnosticMessageSink); + } + + public IEnumerable Discover( + ITestFrameworkDiscoveryOptions discoveryOptions, + ITestMethod testMethod, + IAttributeInfo factAttribute) + { + return _theoryDiscoverer + .Discover(discoveryOptions, testMethod, factAttribute) + .Select(testCase => new MSBuildTestCase(testCase)); + } + } +} diff --git a/test/dotnet-format.Tests/tests/dotnet-format.UnitTests.csproj b/test/dotnet-format.Tests/tests/dotnet-format.UnitTests.csproj new file mode 100644 index 000000000000..f7d9f3506904 --- /dev/null +++ b/test/dotnet-format.Tests/tests/dotnet-format.UnitTests.csproj @@ -0,0 +1,31 @@ + + + + $(NetCurrent) + Microsoft.CodeAnalysis.Tools.Tests + + true + LatestMajor + true + $(DefaultExcludesInProjectFolder);binaries\**;projects\** + Exe + + + + + + + + + + + + + + + + + + + + diff --git a/test/dotnet.Tests/dotnet.Tests.csproj b/test/dotnet.Tests/dotnet.Tests.csproj index dd6e135a4bd9..1a9a46c82936 100644 --- a/test/dotnet.Tests/dotnet.Tests.csproj +++ b/test/dotnet.Tests/dotnet.Tests.csproj @@ -29,7 +29,7 @@ - +