diff --git a/.editorconfig b/.editorconfig index fdd510659..4b4a34520 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,6 +8,8 @@ root = true indent_style = space # (Please don't specify an indent_size here; that has too many unintended consequences.) +file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.\nSee the LICENSE file in the project root for more information. + # Code files [*.{cs,csx,vb,vbx}] indent_size = 4 @@ -30,6 +32,7 @@ indent_size = 2 [*.{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:suggestion dotnet_style_qualification_for_property = false:suggestion @@ -77,4 +80,35 @@ 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 \ No newline at end of file +csharp_new_line_before_members_in_anonymous_types = 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 = do_not_ignore +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 + +[*.cs] +# Disable enforcement of items covered by StyleCop Analyzers +dotnet_style_qualification_for_field = false:none +dotnet_style_qualification_for_property = false:none +dotnet_style_qualification_for_method = false:none +dotnet_style_qualification_for_event = false:none diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..176a458f9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..ce73253ab --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,8 @@ +# Automatically request reviews when a pull request changes any owned files +# More information: https://github.com/blog/2392-introducing-code-owners + +*.groovy @dotnet/roslyn-infrastructure +.github/ @dotnet/roslyn-infrastructure +build/ @dotnet/roslyn-infrastructure +samples/ @dotnet/analyzer-samples +src/ @dotnet/roslyn-sdk diff --git a/.gitignore b/.gitignore index 035570d5e..d392a0a27 100644 --- a/.gitignore +++ b/.gitignore @@ -8,31 +8,23 @@ *.user *.userosscache *.sln.docstates +.vs/ +.vscode/ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# ignore templates modified by build output -src/Templates/VS2015/ModifiedTemplates -src/Templates/VS2017/ModifiedTemplates +artifacts/ +.dotnet/ +.tools/ # Visual Studio 2015 cache/options directory .vs/ # build tools that are created by repo-toolset .tools/ +.packages/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ @@ -53,7 +45,6 @@ dlldata.c # .NET Core project.lock.json project.fragment.lock.json -artifacts/ **/Properties/launchSettings.json *_i.c diff --git a/.vsts-ci.yml b/.vsts-ci.yml new file mode 100644 index 000000000..5f66064b0 --- /dev/null +++ b/.vsts-ci.yml @@ -0,0 +1,128 @@ +resources: +- repo: self + clean: true + +# The variables `_DotNetArtifactsCategory` and `_DotNetValidationArtifactsCategory` are required for proper publishing of build artifacts. See https://github.com/dotnet/roslyn/pull/38259 +variables: + - name: _DotNetArtifactsCategory + value: .NETCore + - name: _DotNetValidationArtifactsCategory + value: .NETCoreValidation + +# Branches that trigger a build on commit +trigger: +- main +- dev17.0 + +stages: +- stage: build + displayName: Build and Test + + jobs: + - template: /eng/common/templates/job/onelocbuild.yml + parameters: + LclSource: lclFilesfromPackage + LclPackageId: 'LCL-JUNO-PROD-ROSLYNSDK' + + - job: OfficialBuild + displayName: Official Build + pool: + name: VSEngSS-MicroBuild2019 + demands: + - cmd + + steps: + - task: ms-vseng.MicroBuildTasks.30666190-6959-11e5-9f96-f56098202fef.MicroBuildSigningPlugin@1 + displayName: Install Signing Plugin + inputs: + signType: $(SignType) + esrpSigning: true + condition: and(succeeded(), ne(variables['SignType'], '')) + + - task: ms-vseng.MicroBuildTasks.32f78468-e895-4f47-962c-58a699361df8.MicroBuildSwixPlugin@1 + displayName: Install Swix Plugin + + - script: eng\common\CIBuild.cmd + -configuration $(BuildConfiguration) + /p:OfficialBuildId=$(Build.BuildNumber) + /p:VisualStudioDropName=$(VisualStudioDropName) + /p:DotNetSignType=$(SignType) + /p:DotNetSymbolServerTokenMsdl=$(microsoft-symbol-server-pat) + /p:DotNetSymbolServerTokenSymWeb=$(symweb-symbol-server-pat) + /p:DotNetArtifactsCategory=$(_DotNetArtifactsCategory) + /p:DotnetPublishUsingPipelines=true + displayName: Build + + - task: PublishTestResults@1 + displayName: Publish Test Results + inputs: + testRunner: XUnit + testResultsFiles: 'artifacts/TestResults/$(BuildConfiguration)/*.xml' + mergeTestResults: true + testRunTitle: 'Unit Tests' + condition: always() + + # Publishes setup VSIXes to a drop. + # Note: The insertion tool looks for the display name of this task in the logs. + - task: ms-vseng.MicroBuildTasks.4305a8de-ba66-4d8b-b2d1-0dc4ecbbf5e8.MicroBuildUploadVstsDropFolder@1 + displayName: Upload VSTS Drop + inputs: + DropName: $(VisualStudioDropName) + DropFolder: 'artifacts\VSSetup\$(BuildConfiguration)\Insertion' + AccessToken: $(System.AccessToken) + condition: succeeded() + + - task: PublishBuildArtifacts@1 + displayName: Publish Logs + inputs: + PathtoPublish: '$(Build.SourcesDirectory)\artifacts\log\$(BuildConfiguration)' + ArtifactName: 'Logs' + continueOnError: true + condition: always() + + # Publish an artifact that the RoslynInsertionTool is able to find by its name. + - task: PublishBuildArtifacts@1 + displayName: Publish Artifact VSSetup + inputs: + PathtoPublish: 'artifacts\VSSetup\$(BuildConfiguration)' + ArtifactName: 'VSSetup' + condition: succeeded() + + # Publish our NuPkgs as an artifact. The name of this artifact must be PackageArtifacts as the + # arcade templates depend on the name. + - task: PublishBuildArtifacts@1 + displayName: Publish Artifact Packages + inputs: + PathtoPublish: 'artifacts\packages\$(BuildConfiguration)' + ArtifactName: 'PackageArtifacts' + condition: succeeded() + + # Publish Asset Manifests for Build Asset Registry job + - task: PublishBuildArtifacts@1 + displayName: Publish Asset Manifests + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/artifacts/log/$(BuildConfiguration)/AssetManifest' + ArtifactName: AssetManifests + condition: succeeded() + + - task: ms-vseng.MicroBuildTasks.521a94ea-9e68-468a-8167-6dcf361ea776.MicroBuildCleanup@1 + displayName: Cleanup + condition: always() + + # Publish to Build Asset Registry + - template: /eng/common/templates/job/publish-build-assets.yml + parameters: + publishUsingPipelines: true + dependsOn: + - OfficialBuild + queue: + name: Hosted VS2017 + +- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - template: eng\common\templates\post-build\post-build.yml + parameters: + publishingInfraVersion: 3 + # Symbol validation is not entirely reliable as of yet, so should be turned off until + # https://github.com/dotnet/arcade/issues/2871 is resolved. + enableSymbolValidation: false + enableSourceLinkValidation: false diff --git a/.vsts-pr.yaml b/.vsts-pr.yaml new file mode 100644 index 000000000..8f1ccdb33 --- /dev/null +++ b/.vsts-pr.yaml @@ -0,0 +1,66 @@ +# Branches that trigger a build on commit +trigger: +- main +- dev17.0 + +# Branches that trigger builds on PR +pr: +- main +- dev17.0 + +variables: + - name: DOTNET_ROOT + value: $(Build.SourcesDirectory)\.dotnet + +jobs: +- job: Windows + pool: + name: NetCorePublic-Pool + queue: BuildPool.Windows.10.Amd64.Open + strategy: + maxParallel: 6 + matrix: + Samples Debug Test: + _args: -test + _configuration: Debug + _solution: Samples + Samples Release Test: + _args: -test + _configuration: Release + _solution: Samples + SDK Debug Test: + _args: -test + _configuration: Debug + _solution: Roslyn-SDK + SDK Release Test: + _args: -test + _configuration: Release + _solution: Roslyn-SDK + SDK Pack: + _args: -pack + _configuration: Release + _solution: Roslyn-SDK + SDK Sign: + _args: -sign + _configuration: Release + _solution: Roslyn-SDK + timeoutInMinutes: 90 + + steps: + - script: eng\PRBuild.cmd $(_args) -configuration $(_configuration) -prepareMachine -projects $(Build.SourcesDirectory)\$(_solution).sln /p:OfficialBuild=false + - task: PublishBuildArtifacts@1 + inputs: + PathtoPublish: '$(Build.SourcesDirectory)\artifacts\log\$(_configuration)' + ArtifactName: '$(_solution) $(_configuration) logs' + publishLocation: Container + continueOnError: true + condition: failed() + - task: PublishTestResults@2 + inputs: + testRunner: 'xUnit' + testResultsFiles: '**/*.xml' + searchFolder: '$(Build.SourcesDirectory)\artifacts\TestResults\$(_configuration)' + configuration: '$(_configuration)' + publishRunAttachments: true + continueOnError: true + condition: and(always(), contains(variables['_args'], '-test')) diff --git a/CODE-OF-CONDUCT.md b/CODE-OF-CONDUCT.md new file mode 100644 index 000000000..775f221c9 --- /dev/null +++ b/CODE-OF-CONDUCT.md @@ -0,0 +1,6 @@ +# Code of Conduct + +This project has adopted the code of conduct defined by the Contributor Covenant +to clarify expected behavior in our community. + +For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). diff --git a/Directory.Build.props b/Directory.Build.props deleted file mode 100644 index 4acc313be..000000000 --- a/Directory.Build.props +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - Debug - - $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)\')) - $(RepoRoot)build\SignToolData.json - $(RepoRoot)build\Versions.props - $(NuGetPackageRoot)RoslynTools.Microsoft.RepoToolset\$(RoslynToolsMicrosoftRepoToolsetVersion)\tools\ - - https://github.com/dotnet/roslyn-sdk - $(RepositoryUrl) - - RoslynDev - true - - \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 6c1ae7260..000000000 --- a/LICENSE.md +++ /dev/null @@ -1,15 +0,0 @@ -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Licensed under the Apache License, Version 2.0 (the “License”); you may not -use this file except in compliance with the License. You may obtain a copy of -the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -License for the specific language governing permissions and limitations under -the License. \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..a616ed188 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 000000000..9680657aa --- /dev/null +++ b/NuGet.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Nuget.Config b/Nuget.Config deleted file mode 100644 index a284d85e8..000000000 --- a/Nuget.Config +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index a6348877d..c73441a30 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,23 @@ -# Roslyn SDK - -|Branch|Unit Tests (Debug)|Unit Tests (Release)| -|---|:--:|:--:| -|[master](https://github.com/dotnet/roslyn-sdk/tree/master)|[![Build Status](https://ci.dot.net/job/Private/job/dotnet_roslyn-sdk/job/master/job/windows_debug//badge/icon)](https://ci.dot.net/job/Private/job/dotnet_roslyn-sdk/job/master/job/windows_debug/)|[![Build Status](https://ci.dot.net/job/Private/job/dotnet_roslyn-sdk/job/master/job/windows_release//badge/icon)](https://ci.dot.net/job/Private/job/dotnet_roslyn-sdk/job/master/job/windows_release/)| - -This repository contains code for both the Roslyn-SDK templates and Syntax Vizualizer. \ No newline at end of file +# Roslyn SDK + +| Branch | Status | +|:------:|:------:| +|dev16.0.x|[![Build Status](https://dnceng.visualstudio.com/public/_apis/build/status/dotnet/roslyn-sdk/public-CI?branchName=dev16.0.x&label=build)](https://dnceng.visualstudio.com/public/_build/latest?definitionId=137&branchName=dev16.0.x)| +|main|[![Build Status](https://dnceng.visualstudio.com/public/_apis/build/status/dotnet/roslyn-sdk/public-CI?branchName=main&label=build)](https://dnceng.visualstudio.com/public/_build/latest?definitionId=137&branchName=main)| + +# What is the Roslyn-SDK? + +Roslyn is the compiler platform for .NET. It consists of the compiler itself and a powerful set of APIs to interact with the compiler. The Roslyn platform is hosted at [github.com/dotnet/roslyn](https://github.com/dotnet/roslyn). The compiler is part of every .NET installation. The APIs to interact with the compiler are available via NuGet (see the [Roslyn repository](https://github.com/dotnet/roslyn) for details). The Roslyn SDK includes additional components to get you started with advanced topics such as distributing a Roslyn analyzer as Visual Studio extension or to inspect code with the Syntax Visualizer. The documentation for the Roslyn platform can be found at [docs.microsoft.com/dotnet/csharp/roslyn-sdk](https://docs.microsoft.com/dotnet/csharp/roslyn-sdk). This repository contains code for both the Roslyn-SDK templates and Syntax Visualizer. + +# Installation instructions + +## Visual Studio 2017 (Version 15.5 and above) + +1. Run **Visual Studio Installer** +2. Hit **Modify** +3. Select the **Individual components** tab +4. Check the box for **.NET Compiler Platform SDK** + +## Visual Studio 2015 + +For older versions of Visual Studio the [.NET Compiler Platform SDK](https://visualstudiogallery.msdn.microsoft.com/2ddb7240-5249-4c8c-969e-5d05823bcb89) is available as an extension in the Visual Studio gallery. diff --git a/Roslyn-SDK.sln b/Roslyn-SDK.sln index d69002509..ae7e94b1b 100644 --- a/Roslyn-SDK.sln +++ b/Roslyn-SDK.sln @@ -1,25 +1,199 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27016.1 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SyntaxVisualizerControl", "src\Tools\SyntaxVisualizer\SyntaxVisualizerControl\SyntaxVisualizerControl.csproj", "{3B6ED9E7-C19E-4501-AAE6-9BE4EA6C18D7}" +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31706.66 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{924F7971-780C-4E70-A306-86482469502E}" EndProject -Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "SyntaxVisualizerDgmlHelper", "src\Tools\SyntaxVisualizer\SyntaxVisualizerDgmlHelper\SyntaxVisualizerDgmlHelper.vbproj", "{A098CF55-9DE1-4C92-ADEC-931CFBFA8536}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VisualStudio.Roslyn.SDK", "VisualStudio.Roslyn.SDK", "{F9B73995-76C6-4056-ADA9-18342F951361}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SyntaxVisualizerExtension", "src\Tools\SyntaxVisualizer\SyntaxVisualizerExtension\SyntaxVisualizerExtension.csproj", "{E268163A-17EB-4C14-A892-868ADFB3108E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roslyn.SDK", "src\VisualStudio.Roslyn.SDK\Roslyn.SDK\Roslyn.SDK.csproj", "{BA2F4AB0-0F9B-48EE-B854-54C38CB91115}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{DC4EAEF7-A2C9-4628-85FA-809F7287E234}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roslyn.SDK.Template.Wizard", "src\VisualStudio.Roslyn.SDK\Roslyn.SDK.Template.Wizard\Roslyn.SDK.Template.Wizard.csproj", "{D060F449-05B9-4830-88A7-4CB879F23A77}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SyntaxVisualizer", "SyntaxVisualizer", "{65AB9A87-CEB0-4CC1-BFFE-FBC393C4DA75}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SyntaxVisualizer", "SyntaxVisualizer", "{8D8DB1B3-0533-4D14-B079-CD2EE922B3F8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Templates.VisualStudio.2015", "src\Templates\VS2015\Templates.VisualStudio.2015.csproj", "{E31F654A-1D4C-4898-8D3C-7A487F82317E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roslyn.SyntaxVisualizer.Control", "src\VisualStudio.Roslyn.SDK\SyntaxVisualizer\Roslyn.SyntaxVisualizer.Control\Roslyn.SyntaxVisualizer.Control.csproj", "{DF972A62-6E0A-4354-B217-C39732B237F9}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Templates", "Templates", "{AB77CD09-4C32-4F58-8F07-915F8B050520}" +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Roslyn.SyntaxVisualizer.DgmlHelper", "src\VisualStudio.Roslyn.SDK\SyntaxVisualizer\Roslyn.SyntaxVisualizer.DgmlHelper\Roslyn.SyntaxVisualizer.DgmlHelper.vbproj", "{DE3A48A7-5368-468A-8EB3-F5E1A3A15EFF}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Templates.VisualStudio.2017", "src\Templates\VS2017\Templates.VisualStudio.2017.csproj", "{44E6D9DB-89FD-41C8-BAEB-5B96AA7793F6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roslyn.SyntaxVisualizer.Extension", "src\VisualStudio.Roslyn.SDK\SyntaxVisualizer\Roslyn.SyntaxVisualizer.Extension\Roslyn.SyntaxVisualizer.Extension.csproj", "{BF3477AA-22C9-4751-A4C0-01EF30728AE9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RoslynSDKTemplateWizard", "src\Tools\RoslynSDKTemplateWizard\RoslynSDKTemplateWizard.csproj", "{ECFF0F53-89D5-4D7D-95D0-EAC1514D84E8}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.CodeAnalysis.Testing", "Microsoft.CodeAnalysis.Testing", "{A3E08CE3-2358-4D36-875B-82C99AC61CD8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Analyzer.Testing", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.Analyzer.Testing\Microsoft.CodeAnalysis.Analyzer.Testing.csproj", "{36F8BB88-BBDB-4917-BCF4-269E7652A314}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CodeFix.Testing", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CodeFix.Testing\Microsoft.CodeAnalysis.CodeFix.Testing.csproj", "{F2892BD1-6FB9-4346-89A2-EC4C3076FC9E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.Analyzer.Testing", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.Analyzer.Testing\Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.csproj", "{F8785C36-69E1-423B-9E4A-36DF8A9CF904}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest\Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest.csproj", "{2B77DAF6-67B2-47A0-91DC-94383C46C355}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit\Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit.csproj", "{14C162CB-CD1D-4122-B123-D0A3AA91BEA8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit\Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit.csproj", "{87CFFE11-B7A1-4F08-971A-2D1F6A599E29}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.CodeFix.Testing", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.CodeFix.Testing\Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.csproj", "{7B7D8972-B167-4338-B112-2BB37DBDBF71}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest\Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest.csproj", "{96A81378-0E48-497E-A712-92114D177691}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit\Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit.csproj", "{C429944E-EC27-42BD-8687-2C126B76861B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit\Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit.csproj", "{8E81D799-F8BF-443E-A25B-CEC06B4BA05E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Testing.Verifiers.MSTest", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.Testing.Verifiers.MSTest\Microsoft.CodeAnalysis.Testing.Verifiers.MSTest.csproj", "{F0E3E515-F604-4EE5-A013-6648F39322E4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Testing.Verifiers.NUnit", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.Testing.Verifiers.NUnit\Microsoft.CodeAnalysis.Testing.Verifiers.NUnit.csproj", "{22E65BCC-B91F-4CD5-AC5B-CB71AEF8ACDC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Testing.Verifiers.XUnit", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.Testing.Verifiers.XUnit\Microsoft.CodeAnalysis.Testing.Verifiers.XUnit.csproj", "{19D21A12-B2B6-4A99-8A57-C5E2C3C56192}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing\Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.vbproj", "{0FF93648-F249-4F25-AF46-15C96168682B}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest\Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest.vbproj", "{7A28B74F-B3AC-43B9-B2E3-581316DCE809}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit\Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit.vbproj", "{A42D58EA-6C03-401C-B3E8-9671DD66FAB4}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit\Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit.vbproj", "{6E5011E7-13B3-4A74-863D-35356229616A}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing\Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.vbproj", "{D130C11C-6E09-4D2D-BFB7-C40545E26C1B}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest\Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest.vbproj", "{ED49E6AD-3E1D-48FB-9DEF-FB8B53DA7179}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit\Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit.vbproj", "{9ECFBBD5-F7DD-4ECF-A738-41E2F747D67A}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit\Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit.vbproj", "{F08AEF4C-3314-4A26-87D7-8A6F52482C24}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{8C343846-5F9F-4033-9B52-B44C61962449}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.CodeAnalysis.Testing", "Microsoft.CodeAnalysis.Testing", "{9905147E-CC1F-42A0-BD27-05586C583DF7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Analyzer.Testing.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.Analyzer.Testing.UnitTests\Microsoft.CodeAnalysis.Analyzer.Testing.UnitTests.csproj", "{FE26E824-EC4D-4CF9-9927-7E4B81502349}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CodeFix.Testing.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CodeFix.Testing.UnitTests\Microsoft.CodeAnalysis.CodeFix.Testing.UnitTests.csproj", "{56490F48-5770-4EAB-81A3-AF915398871C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.UnitTests\Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.UnitTests.csproj", "{78102FBF-4418-42FE-ACBA-A23BB2706F0A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit.UnitTests\Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit.UnitTests.csproj", "{EBB5F8C1-5220-4D45-88AF-AC97D77AA14C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.UnitTests\Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.UnitTests.csproj", "{0DD2AF99-AAA6-448A-8A9C-16EE373AFDAB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit.UnitTests\Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit.UnitTests.csproj", "{948BC599-48DC-4E41-8B82-58E0363B0C82}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Testing.Utilities", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.Testing.Utilities\Microsoft.CodeAnalysis.Testing.Utilities.csproj", "{5DE03057-D112-4D96-AB25-9B295405AFA7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Testing.Verifiers.XUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.Testing.Verifiers.XUnit.UnitTests\Microsoft.CodeAnalysis.Testing.Verifiers.XUnit.UnitTests.csproj", "{D4769BE4-D7CF-4DF0-AE6D-19271AAA523A}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.UnitTests\Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.UnitTests.vbproj", "{64B8D716-2D65-4324-9F49-C400BFD44A27}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit.UnitTests\Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit.UnitTests.vbproj", "{644A619F-E11D-4EAF-9CB2-7C8D3D19D767}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.UnitTests\Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.UnitTests.vbproj", "{CB54BA8F-2484-4E94-9950-2B293928CD9B}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit.UnitTests\Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit.UnitTests.vbproj", "{3DECEADE-4167-49BB-97ED-9D67AC9621AF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CodeRefactoring.Testing", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CodeRefactoring.Testing\Microsoft.CodeAnalysis.CodeRefactoring.Testing.csproj", "{E4A13773-25AC-4CBA-B853-156FF04A5F93}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing\Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.csproj", "{2B2A5675-EA12-4921-A075-5C8BCB10D02D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest\Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest.csproj", "{09F74B4D-8BC9-43D1-8B64-696D055E0B81}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit\Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit.csproj", "{5D46448D-1D58-46CB-BA0F-04AD763AC40D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit\Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit.csproj", "{6028A9D4-7197-4361-A394-BADA39B061B3}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing\Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.vbproj", "{7642812C-564D-40CD-985A-F53EC821CBE3}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest\Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest.vbproj", "{6BCBD409-3C28-450D-AA27-D4E663BFE902}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit\Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit.vbproj", "{065D8144-FE05-4980-9C90-E67F682770CE}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit\Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit.vbproj", "{2F77A172-1944-4D02-BB72-696FE30778E6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CodeRefactoring.Testing.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CodeRefactoring.Testing.UnitTests\Microsoft.CodeAnalysis.CodeRefactoring.Testing.UnitTests.csproj", "{8E9B5FE5-6C6B-4CC2-816B-8CDA6F3D1297}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.UnitTests\Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.UnitTests.csproj", "{995E5475-2032-4A23-A85B-F8400C02CAFE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit.UnitTests\Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit.UnitTests.csproj", "{E8C5489A-5EBA-4471-ADCA-1CE3F1268AE8}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.UnitTests\Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.UnitTests.vbproj", "{68DEB3AF-8572-45A9-9EED-E71B5F1F35CA}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit.UnitTests\Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit.UnitTests.vbproj", "{A4812432-D0FE-4946-BF92-38E60B49B88D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest.UnitTests\Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest.UnitTests.csproj", "{D307A5F5-5652-4AD9-B463-2C0798BEBF3F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest.UnitTests\Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest.UnitTests.csproj", "{A9DA4D84-BEB9-4387-93A0-FD4136426A43}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest.UnitTests\Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest.UnitTests.vbproj", "{585D7F21-C0B1-41C3-A8B5-01545FEFDF6C}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest.UnitTests\Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest.UnitTests.vbproj", "{F1565840-5AF1-4CCA-A7AF-DA8AED89CE3E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest.UnitTests\Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest.UnitTests.csproj", "{1FDA0F25-0050-4A45-B20F-FEC93EC1FB98}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest.UnitTests\Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest.UnitTests.vbproj", "{F3ACBF36-B31B-4C17-8DB0-931B03EEFFDF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Testing.Verifiers.MSTest.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.Testing.Verifiers.MSTest.UnitTests\Microsoft.CodeAnalysis.Testing.Verifiers.MSTest.UnitTests.csproj", "{85F1F599-330B-4C3D-839E-17D5E3587C16}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Testing.Verifiers.NUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.Testing.Verifiers.NUnit.UnitTests\Microsoft.CodeAnalysis.Testing.Verifiers.NUnit.UnitTests.csproj", "{F148144A-15F7-4920-B2B5-5182250AF6B3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit.UnitTests\Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit.UnitTests.csproj", "{D01D8667-B0F3-426D-BC4E-78789199F757}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit.UnitTests\Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit.UnitTests.csproj", "{F8D2D0D9-513C-4EE2-871C-1494837DE696}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit.UnitTests\Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit.UnitTests.csproj", "{7E65A7AA-B615-4C92-95E6-39C64933517D}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit.UnitTests\Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit.UnitTests.vbproj", "{19DB3193-B920-4C62-ADC1-5071AE989AA5}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit.UnitTests\Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit.UnitTests.vbproj", "{4617ED77-9564-4A06-8F9B-92E5C5523FE1}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit.UnitTests\Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit.UnitTests.vbproj", "{023B21F8-09EC-4A67-8AAA-3D110231E7EB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VisualStudio.Roslyn.SDK", "VisualStudio.Roslyn.SDK", "{A3AF556C-276C-49BA-A9ED-E7D42FECAA46}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roslyn.SDK.IntegrationTests", "tests\VisualStudio.Roslyn.SDK\Roslyn.SDK.IntegrationTests\Roslyn.SDK.IntegrationTests.csproj", "{6DBBFF7B-2C28-47D7-8618-B6085044E38D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roslyn.SDK.UnitTests", "tests\VisualStudio.Roslyn.SDK\Roslyn.SDK.UnitTests\Roslyn.SDK.UnitTests.csproj", "{11B1F856-9025-4A4C-B90D-B1237743B672}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.SourceGenerators.Testing", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.SourceGenerators.Testing\Microsoft.CodeAnalysis.SourceGenerators.Testing.csproj", "{05A91267-ABC8-4249-9A04-166C08EAD849}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing\Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.csproj", "{99D7BB0C-DE8D-4952-A9BF-63A5215256C4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest\Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest.csproj", "{31886751-5615-435C-A40C-EA2CC415BBDD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit\Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit.csproj", "{92558FFF-0935-4B12-8ED6-E3DC4E486B7F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit\Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit.csproj", "{5BC27DBC-6727-4DA1-B1AF-5EA5D3803627}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing\Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.vbproj", "{EC96BD3F-2DFE-4072-86F5-BFB349FF77D9}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest\Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest.vbproj", "{74F9C0F1-2E95-4EC8-B395-EB73AC4DD42B}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit\Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit.vbproj", "{A7D3307A-0A0D-4D20-887A-9C830A38B058}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit", "src\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit\Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit.vbproj", "{4B158F47-4759-495B-83BB-1D87130E3DF8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests\Microsoft.CodeAnalysis.SourceGenerators.Testing.UnitTests.csproj", "{68CAEBF3-D428-4B50-8305-4BE4F7753CA8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.UnitTests\Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.UnitTests.csproj", "{0E75580E-10E6-4CAC-87C2-D83C6C0B1D10}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest.UnitTests\Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest.UnitTests.csproj", "{4E02E29B-54C7-4576-8575-0438EABCCC88}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit.UnitTests\Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit.UnitTests.csproj", "{485C6D8A-271F-44CD-9F55-B5283A917A00}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit.UnitTests\Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit.UnitTests.csproj", "{074F9FC2-B0E0-40E4-B356-99F0E07E279B}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.UnitTests\Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.UnitTests.vbproj", "{ED7BCD8E-F553-48F0-962F-019BE7C2B78E}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest.UnitTests\Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest.UnitTests.vbproj", "{7D9C0EF5-7383-4E35-811B-3288B3C806F3}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit.UnitTests\Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit.UnitTests.vbproj", "{7C3FE60E-055B-4E0C-BB85-C7E94A640074}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit.UnitTests\Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit.UnitTests.vbproj", "{92BD1781-5DB4-4F72-BCCB-0D64C0790A2B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roslyn.ComponentDebugger", "src\VisualStudio.Roslyn.SDK\ComponentDebugger\Roslyn.ComponentDebugger.csproj", "{421DE59C-8246-4679-9D69-79F16A7187BE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "util", "util", "{7A94E723-ADD6-48C4-BBE7-1D5B311187A8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssemblyVersionGenerator", "src\VisualStudio.Roslyn.SDK\AssemblyVersionGenerator\AssemblyVersionGenerator.csproj", "{AB6B3C69-9F6F-461C-BFD8-D3F25B9F44AD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -27,44 +201,458 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {3B6ED9E7-C19E-4501-AAE6-9BE4EA6C18D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3B6ED9E7-C19E-4501-AAE6-9BE4EA6C18D7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3B6ED9E7-C19E-4501-AAE6-9BE4EA6C18D7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3B6ED9E7-C19E-4501-AAE6-9BE4EA6C18D7}.Release|Any CPU.Build.0 = Release|Any CPU - {A098CF55-9DE1-4C92-ADEC-931CFBFA8536}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A098CF55-9DE1-4C92-ADEC-931CFBFA8536}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A098CF55-9DE1-4C92-ADEC-931CFBFA8536}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A098CF55-9DE1-4C92-ADEC-931CFBFA8536}.Release|Any CPU.Build.0 = Release|Any CPU - {E268163A-17EB-4C14-A892-868ADFB3108E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E268163A-17EB-4C14-A892-868ADFB3108E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E268163A-17EB-4C14-A892-868ADFB3108E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E268163A-17EB-4C14-A892-868ADFB3108E}.Release|Any CPU.Build.0 = Release|Any CPU - {E31F654A-1D4C-4898-8D3C-7A487F82317E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E31F654A-1D4C-4898-8D3C-7A487F82317E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E31F654A-1D4C-4898-8D3C-7A487F82317E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E31F654A-1D4C-4898-8D3C-7A487F82317E}.Release|Any CPU.Build.0 = Release|Any CPU - {44E6D9DB-89FD-41C8-BAEB-5B96AA7793F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {44E6D9DB-89FD-41C8-BAEB-5B96AA7793F6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {44E6D9DB-89FD-41C8-BAEB-5B96AA7793F6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {44E6D9DB-89FD-41C8-BAEB-5B96AA7793F6}.Release|Any CPU.Build.0 = Release|Any CPU - {ECFF0F53-89D5-4D7D-95D0-EAC1514D84E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ECFF0F53-89D5-4D7D-95D0-EAC1514D84E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ECFF0F53-89D5-4D7D-95D0-EAC1514D84E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ECFF0F53-89D5-4D7D-95D0-EAC1514D84E8}.Release|Any CPU.Build.0 = Release|Any CPU + {BA2F4AB0-0F9B-48EE-B854-54C38CB91115}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA2F4AB0-0F9B-48EE-B854-54C38CB91115}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA2F4AB0-0F9B-48EE-B854-54C38CB91115}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA2F4AB0-0F9B-48EE-B854-54C38CB91115}.Release|Any CPU.Build.0 = Release|Any CPU + {D060F449-05B9-4830-88A7-4CB879F23A77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D060F449-05B9-4830-88A7-4CB879F23A77}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D060F449-05B9-4830-88A7-4CB879F23A77}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D060F449-05B9-4830-88A7-4CB879F23A77}.Release|Any CPU.Build.0 = Release|Any CPU + {DF972A62-6E0A-4354-B217-C39732B237F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF972A62-6E0A-4354-B217-C39732B237F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF972A62-6E0A-4354-B217-C39732B237F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF972A62-6E0A-4354-B217-C39732B237F9}.Release|Any CPU.Build.0 = Release|Any CPU + {DE3A48A7-5368-468A-8EB3-F5E1A3A15EFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE3A48A7-5368-468A-8EB3-F5E1A3A15EFF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE3A48A7-5368-468A-8EB3-F5E1A3A15EFF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE3A48A7-5368-468A-8EB3-F5E1A3A15EFF}.Release|Any CPU.Build.0 = Release|Any CPU + {BF3477AA-22C9-4751-A4C0-01EF30728AE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF3477AA-22C9-4751-A4C0-01EF30728AE9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF3477AA-22C9-4751-A4C0-01EF30728AE9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF3477AA-22C9-4751-A4C0-01EF30728AE9}.Release|Any CPU.Build.0 = Release|Any CPU + {36F8BB88-BBDB-4917-BCF4-269E7652A314}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {36F8BB88-BBDB-4917-BCF4-269E7652A314}.Debug|Any CPU.Build.0 = Debug|Any CPU + {36F8BB88-BBDB-4917-BCF4-269E7652A314}.Release|Any CPU.ActiveCfg = Release|Any CPU + {36F8BB88-BBDB-4917-BCF4-269E7652A314}.Release|Any CPU.Build.0 = Release|Any CPU + {F2892BD1-6FB9-4346-89A2-EC4C3076FC9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2892BD1-6FB9-4346-89A2-EC4C3076FC9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2892BD1-6FB9-4346-89A2-EC4C3076FC9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2892BD1-6FB9-4346-89A2-EC4C3076FC9E}.Release|Any CPU.Build.0 = Release|Any CPU + {F8785C36-69E1-423B-9E4A-36DF8A9CF904}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8785C36-69E1-423B-9E4A-36DF8A9CF904}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8785C36-69E1-423B-9E4A-36DF8A9CF904}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8785C36-69E1-423B-9E4A-36DF8A9CF904}.Release|Any CPU.Build.0 = Release|Any CPU + {2B77DAF6-67B2-47A0-91DC-94383C46C355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B77DAF6-67B2-47A0-91DC-94383C46C355}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B77DAF6-67B2-47A0-91DC-94383C46C355}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B77DAF6-67B2-47A0-91DC-94383C46C355}.Release|Any CPU.Build.0 = Release|Any CPU + {14C162CB-CD1D-4122-B123-D0A3AA91BEA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14C162CB-CD1D-4122-B123-D0A3AA91BEA8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14C162CB-CD1D-4122-B123-D0A3AA91BEA8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14C162CB-CD1D-4122-B123-D0A3AA91BEA8}.Release|Any CPU.Build.0 = Release|Any CPU + {87CFFE11-B7A1-4F08-971A-2D1F6A599E29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {87CFFE11-B7A1-4F08-971A-2D1F6A599E29}.Debug|Any CPU.Build.0 = Debug|Any CPU + {87CFFE11-B7A1-4F08-971A-2D1F6A599E29}.Release|Any CPU.ActiveCfg = Release|Any CPU + {87CFFE11-B7A1-4F08-971A-2D1F6A599E29}.Release|Any CPU.Build.0 = Release|Any CPU + {7B7D8972-B167-4338-B112-2BB37DBDBF71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B7D8972-B167-4338-B112-2BB37DBDBF71}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B7D8972-B167-4338-B112-2BB37DBDBF71}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B7D8972-B167-4338-B112-2BB37DBDBF71}.Release|Any CPU.Build.0 = Release|Any CPU + {96A81378-0E48-497E-A712-92114D177691}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96A81378-0E48-497E-A712-92114D177691}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96A81378-0E48-497E-A712-92114D177691}.Release|Any CPU.ActiveCfg = Release|Any CPU + {96A81378-0E48-497E-A712-92114D177691}.Release|Any CPU.Build.0 = Release|Any CPU + {C429944E-EC27-42BD-8687-2C126B76861B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C429944E-EC27-42BD-8687-2C126B76861B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C429944E-EC27-42BD-8687-2C126B76861B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C429944E-EC27-42BD-8687-2C126B76861B}.Release|Any CPU.Build.0 = Release|Any CPU + {8E81D799-F8BF-443E-A25B-CEC06B4BA05E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8E81D799-F8BF-443E-A25B-CEC06B4BA05E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8E81D799-F8BF-443E-A25B-CEC06B4BA05E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8E81D799-F8BF-443E-A25B-CEC06B4BA05E}.Release|Any CPU.Build.0 = Release|Any CPU + {F0E3E515-F604-4EE5-A013-6648F39322E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F0E3E515-F604-4EE5-A013-6648F39322E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F0E3E515-F604-4EE5-A013-6648F39322E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F0E3E515-F604-4EE5-A013-6648F39322E4}.Release|Any CPU.Build.0 = Release|Any CPU + {22E65BCC-B91F-4CD5-AC5B-CB71AEF8ACDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22E65BCC-B91F-4CD5-AC5B-CB71AEF8ACDC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22E65BCC-B91F-4CD5-AC5B-CB71AEF8ACDC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22E65BCC-B91F-4CD5-AC5B-CB71AEF8ACDC}.Release|Any CPU.Build.0 = Release|Any CPU + {19D21A12-B2B6-4A99-8A57-C5E2C3C56192}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19D21A12-B2B6-4A99-8A57-C5E2C3C56192}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19D21A12-B2B6-4A99-8A57-C5E2C3C56192}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19D21A12-B2B6-4A99-8A57-C5E2C3C56192}.Release|Any CPU.Build.0 = Release|Any CPU + {0FF93648-F249-4F25-AF46-15C96168682B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0FF93648-F249-4F25-AF46-15C96168682B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0FF93648-F249-4F25-AF46-15C96168682B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0FF93648-F249-4F25-AF46-15C96168682B}.Release|Any CPU.Build.0 = Release|Any CPU + {7A28B74F-B3AC-43B9-B2E3-581316DCE809}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7A28B74F-B3AC-43B9-B2E3-581316DCE809}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7A28B74F-B3AC-43B9-B2E3-581316DCE809}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7A28B74F-B3AC-43B9-B2E3-581316DCE809}.Release|Any CPU.Build.0 = Release|Any CPU + {A42D58EA-6C03-401C-B3E8-9671DD66FAB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A42D58EA-6C03-401C-B3E8-9671DD66FAB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A42D58EA-6C03-401C-B3E8-9671DD66FAB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A42D58EA-6C03-401C-B3E8-9671DD66FAB4}.Release|Any CPU.Build.0 = Release|Any CPU + {6E5011E7-13B3-4A74-863D-35356229616A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E5011E7-13B3-4A74-863D-35356229616A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E5011E7-13B3-4A74-863D-35356229616A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E5011E7-13B3-4A74-863D-35356229616A}.Release|Any CPU.Build.0 = Release|Any CPU + {D130C11C-6E09-4D2D-BFB7-C40545E26C1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D130C11C-6E09-4D2D-BFB7-C40545E26C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D130C11C-6E09-4D2D-BFB7-C40545E26C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D130C11C-6E09-4D2D-BFB7-C40545E26C1B}.Release|Any CPU.Build.0 = Release|Any CPU + {ED49E6AD-3E1D-48FB-9DEF-FB8B53DA7179}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED49E6AD-3E1D-48FB-9DEF-FB8B53DA7179}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED49E6AD-3E1D-48FB-9DEF-FB8B53DA7179}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED49E6AD-3E1D-48FB-9DEF-FB8B53DA7179}.Release|Any CPU.Build.0 = Release|Any CPU + {9ECFBBD5-F7DD-4ECF-A738-41E2F747D67A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9ECFBBD5-F7DD-4ECF-A738-41E2F747D67A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9ECFBBD5-F7DD-4ECF-A738-41E2F747D67A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9ECFBBD5-F7DD-4ECF-A738-41E2F747D67A}.Release|Any CPU.Build.0 = Release|Any CPU + {F08AEF4C-3314-4A26-87D7-8A6F52482C24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F08AEF4C-3314-4A26-87D7-8A6F52482C24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F08AEF4C-3314-4A26-87D7-8A6F52482C24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F08AEF4C-3314-4A26-87D7-8A6F52482C24}.Release|Any CPU.Build.0 = Release|Any CPU + {FE26E824-EC4D-4CF9-9927-7E4B81502349}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE26E824-EC4D-4CF9-9927-7E4B81502349}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE26E824-EC4D-4CF9-9927-7E4B81502349}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE26E824-EC4D-4CF9-9927-7E4B81502349}.Release|Any CPU.Build.0 = Release|Any CPU + {56490F48-5770-4EAB-81A3-AF915398871C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56490F48-5770-4EAB-81A3-AF915398871C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56490F48-5770-4EAB-81A3-AF915398871C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56490F48-5770-4EAB-81A3-AF915398871C}.Release|Any CPU.Build.0 = Release|Any CPU + {78102FBF-4418-42FE-ACBA-A23BB2706F0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78102FBF-4418-42FE-ACBA-A23BB2706F0A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78102FBF-4418-42FE-ACBA-A23BB2706F0A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78102FBF-4418-42FE-ACBA-A23BB2706F0A}.Release|Any CPU.Build.0 = Release|Any CPU + {EBB5F8C1-5220-4D45-88AF-AC97D77AA14C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBB5F8C1-5220-4D45-88AF-AC97D77AA14C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBB5F8C1-5220-4D45-88AF-AC97D77AA14C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBB5F8C1-5220-4D45-88AF-AC97D77AA14C}.Release|Any CPU.Build.0 = Release|Any CPU + {0DD2AF99-AAA6-448A-8A9C-16EE373AFDAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DD2AF99-AAA6-448A-8A9C-16EE373AFDAB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DD2AF99-AAA6-448A-8A9C-16EE373AFDAB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DD2AF99-AAA6-448A-8A9C-16EE373AFDAB}.Release|Any CPU.Build.0 = Release|Any CPU + {948BC599-48DC-4E41-8B82-58E0363B0C82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {948BC599-48DC-4E41-8B82-58E0363B0C82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {948BC599-48DC-4E41-8B82-58E0363B0C82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {948BC599-48DC-4E41-8B82-58E0363B0C82}.Release|Any CPU.Build.0 = Release|Any CPU + {5DE03057-D112-4D96-AB25-9B295405AFA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DE03057-D112-4D96-AB25-9B295405AFA7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DE03057-D112-4D96-AB25-9B295405AFA7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DE03057-D112-4D96-AB25-9B295405AFA7}.Release|Any CPU.Build.0 = Release|Any CPU + {D4769BE4-D7CF-4DF0-AE6D-19271AAA523A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4769BE4-D7CF-4DF0-AE6D-19271AAA523A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4769BE4-D7CF-4DF0-AE6D-19271AAA523A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4769BE4-D7CF-4DF0-AE6D-19271AAA523A}.Release|Any CPU.Build.0 = Release|Any CPU + {64B8D716-2D65-4324-9F49-C400BFD44A27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {64B8D716-2D65-4324-9F49-C400BFD44A27}.Debug|Any CPU.Build.0 = Debug|Any CPU + {64B8D716-2D65-4324-9F49-C400BFD44A27}.Release|Any CPU.ActiveCfg = Release|Any CPU + {64B8D716-2D65-4324-9F49-C400BFD44A27}.Release|Any CPU.Build.0 = Release|Any CPU + {644A619F-E11D-4EAF-9CB2-7C8D3D19D767}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {644A619F-E11D-4EAF-9CB2-7C8D3D19D767}.Debug|Any CPU.Build.0 = Debug|Any CPU + {644A619F-E11D-4EAF-9CB2-7C8D3D19D767}.Release|Any CPU.ActiveCfg = Release|Any CPU + {644A619F-E11D-4EAF-9CB2-7C8D3D19D767}.Release|Any CPU.Build.0 = Release|Any CPU + {CB54BA8F-2484-4E94-9950-2B293928CD9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB54BA8F-2484-4E94-9950-2B293928CD9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB54BA8F-2484-4E94-9950-2B293928CD9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB54BA8F-2484-4E94-9950-2B293928CD9B}.Release|Any CPU.Build.0 = Release|Any CPU + {3DECEADE-4167-49BB-97ED-9D67AC9621AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DECEADE-4167-49BB-97ED-9D67AC9621AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DECEADE-4167-49BB-97ED-9D67AC9621AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DECEADE-4167-49BB-97ED-9D67AC9621AF}.Release|Any CPU.Build.0 = Release|Any CPU + {E4A13773-25AC-4CBA-B853-156FF04A5F93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4A13773-25AC-4CBA-B853-156FF04A5F93}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4A13773-25AC-4CBA-B853-156FF04A5F93}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4A13773-25AC-4CBA-B853-156FF04A5F93}.Release|Any CPU.Build.0 = Release|Any CPU + {2B2A5675-EA12-4921-A075-5C8BCB10D02D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B2A5675-EA12-4921-A075-5C8BCB10D02D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B2A5675-EA12-4921-A075-5C8BCB10D02D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B2A5675-EA12-4921-A075-5C8BCB10D02D}.Release|Any CPU.Build.0 = Release|Any CPU + {09F74B4D-8BC9-43D1-8B64-696D055E0B81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09F74B4D-8BC9-43D1-8B64-696D055E0B81}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09F74B4D-8BC9-43D1-8B64-696D055E0B81}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09F74B4D-8BC9-43D1-8B64-696D055E0B81}.Release|Any CPU.Build.0 = Release|Any CPU + {5D46448D-1D58-46CB-BA0F-04AD763AC40D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D46448D-1D58-46CB-BA0F-04AD763AC40D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D46448D-1D58-46CB-BA0F-04AD763AC40D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D46448D-1D58-46CB-BA0F-04AD763AC40D}.Release|Any CPU.Build.0 = Release|Any CPU + {6028A9D4-7197-4361-A394-BADA39B061B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6028A9D4-7197-4361-A394-BADA39B061B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6028A9D4-7197-4361-A394-BADA39B061B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6028A9D4-7197-4361-A394-BADA39B061B3}.Release|Any CPU.Build.0 = Release|Any CPU + {7642812C-564D-40CD-985A-F53EC821CBE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7642812C-564D-40CD-985A-F53EC821CBE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7642812C-564D-40CD-985A-F53EC821CBE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7642812C-564D-40CD-985A-F53EC821CBE3}.Release|Any CPU.Build.0 = Release|Any CPU + {6BCBD409-3C28-450D-AA27-D4E663BFE902}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6BCBD409-3C28-450D-AA27-D4E663BFE902}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6BCBD409-3C28-450D-AA27-D4E663BFE902}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6BCBD409-3C28-450D-AA27-D4E663BFE902}.Release|Any CPU.Build.0 = Release|Any CPU + {065D8144-FE05-4980-9C90-E67F682770CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {065D8144-FE05-4980-9C90-E67F682770CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {065D8144-FE05-4980-9C90-E67F682770CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {065D8144-FE05-4980-9C90-E67F682770CE}.Release|Any CPU.Build.0 = Release|Any CPU + {2F77A172-1944-4D02-BB72-696FE30778E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F77A172-1944-4D02-BB72-696FE30778E6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F77A172-1944-4D02-BB72-696FE30778E6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F77A172-1944-4D02-BB72-696FE30778E6}.Release|Any CPU.Build.0 = Release|Any CPU + {8E9B5FE5-6C6B-4CC2-816B-8CDA6F3D1297}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8E9B5FE5-6C6B-4CC2-816B-8CDA6F3D1297}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8E9B5FE5-6C6B-4CC2-816B-8CDA6F3D1297}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8E9B5FE5-6C6B-4CC2-816B-8CDA6F3D1297}.Release|Any CPU.Build.0 = Release|Any CPU + {995E5475-2032-4A23-A85B-F8400C02CAFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {995E5475-2032-4A23-A85B-F8400C02CAFE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {995E5475-2032-4A23-A85B-F8400C02CAFE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {995E5475-2032-4A23-A85B-F8400C02CAFE}.Release|Any CPU.Build.0 = Release|Any CPU + {E8C5489A-5EBA-4471-ADCA-1CE3F1268AE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E8C5489A-5EBA-4471-ADCA-1CE3F1268AE8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E8C5489A-5EBA-4471-ADCA-1CE3F1268AE8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E8C5489A-5EBA-4471-ADCA-1CE3F1268AE8}.Release|Any CPU.Build.0 = Release|Any CPU + {68DEB3AF-8572-45A9-9EED-E71B5F1F35CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68DEB3AF-8572-45A9-9EED-E71B5F1F35CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68DEB3AF-8572-45A9-9EED-E71B5F1F35CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68DEB3AF-8572-45A9-9EED-E71B5F1F35CA}.Release|Any CPU.Build.0 = Release|Any CPU + {A4812432-D0FE-4946-BF92-38E60B49B88D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4812432-D0FE-4946-BF92-38E60B49B88D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4812432-D0FE-4946-BF92-38E60B49B88D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4812432-D0FE-4946-BF92-38E60B49B88D}.Release|Any CPU.Build.0 = Release|Any CPU + {D307A5F5-5652-4AD9-B463-2C0798BEBF3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D307A5F5-5652-4AD9-B463-2C0798BEBF3F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D307A5F5-5652-4AD9-B463-2C0798BEBF3F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D307A5F5-5652-4AD9-B463-2C0798BEBF3F}.Release|Any CPU.Build.0 = Release|Any CPU + {A9DA4D84-BEB9-4387-93A0-FD4136426A43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A9DA4D84-BEB9-4387-93A0-FD4136426A43}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A9DA4D84-BEB9-4387-93A0-FD4136426A43}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A9DA4D84-BEB9-4387-93A0-FD4136426A43}.Release|Any CPU.Build.0 = Release|Any CPU + {585D7F21-C0B1-41C3-A8B5-01545FEFDF6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {585D7F21-C0B1-41C3-A8B5-01545FEFDF6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {585D7F21-C0B1-41C3-A8B5-01545FEFDF6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {585D7F21-C0B1-41C3-A8B5-01545FEFDF6C}.Release|Any CPU.Build.0 = Release|Any CPU + {F1565840-5AF1-4CCA-A7AF-DA8AED89CE3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1565840-5AF1-4CCA-A7AF-DA8AED89CE3E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1565840-5AF1-4CCA-A7AF-DA8AED89CE3E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1565840-5AF1-4CCA-A7AF-DA8AED89CE3E}.Release|Any CPU.Build.0 = Release|Any CPU + {1FDA0F25-0050-4A45-B20F-FEC93EC1FB98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FDA0F25-0050-4A45-B20F-FEC93EC1FB98}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FDA0F25-0050-4A45-B20F-FEC93EC1FB98}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FDA0F25-0050-4A45-B20F-FEC93EC1FB98}.Release|Any CPU.Build.0 = Release|Any CPU + {F3ACBF36-B31B-4C17-8DB0-931B03EEFFDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3ACBF36-B31B-4C17-8DB0-931B03EEFFDF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3ACBF36-B31B-4C17-8DB0-931B03EEFFDF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3ACBF36-B31B-4C17-8DB0-931B03EEFFDF}.Release|Any CPU.Build.0 = Release|Any CPU + {85F1F599-330B-4C3D-839E-17D5E3587C16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85F1F599-330B-4C3D-839E-17D5E3587C16}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85F1F599-330B-4C3D-839E-17D5E3587C16}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85F1F599-330B-4C3D-839E-17D5E3587C16}.Release|Any CPU.Build.0 = Release|Any CPU + {F148144A-15F7-4920-B2B5-5182250AF6B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F148144A-15F7-4920-B2B5-5182250AF6B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F148144A-15F7-4920-B2B5-5182250AF6B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F148144A-15F7-4920-B2B5-5182250AF6B3}.Release|Any CPU.Build.0 = Release|Any CPU + {D01D8667-B0F3-426D-BC4E-78789199F757}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D01D8667-B0F3-426D-BC4E-78789199F757}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D01D8667-B0F3-426D-BC4E-78789199F757}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D01D8667-B0F3-426D-BC4E-78789199F757}.Release|Any CPU.Build.0 = Release|Any CPU + {F8D2D0D9-513C-4EE2-871C-1494837DE696}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8D2D0D9-513C-4EE2-871C-1494837DE696}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8D2D0D9-513C-4EE2-871C-1494837DE696}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8D2D0D9-513C-4EE2-871C-1494837DE696}.Release|Any CPU.Build.0 = Release|Any CPU + {7E65A7AA-B615-4C92-95E6-39C64933517D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7E65A7AA-B615-4C92-95E6-39C64933517D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E65A7AA-B615-4C92-95E6-39C64933517D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7E65A7AA-B615-4C92-95E6-39C64933517D}.Release|Any CPU.Build.0 = Release|Any CPU + {19DB3193-B920-4C62-ADC1-5071AE989AA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19DB3193-B920-4C62-ADC1-5071AE989AA5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19DB3193-B920-4C62-ADC1-5071AE989AA5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19DB3193-B920-4C62-ADC1-5071AE989AA5}.Release|Any CPU.Build.0 = Release|Any CPU + {4617ED77-9564-4A06-8F9B-92E5C5523FE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4617ED77-9564-4A06-8F9B-92E5C5523FE1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4617ED77-9564-4A06-8F9B-92E5C5523FE1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4617ED77-9564-4A06-8F9B-92E5C5523FE1}.Release|Any CPU.Build.0 = Release|Any CPU + {023B21F8-09EC-4A67-8AAA-3D110231E7EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {023B21F8-09EC-4A67-8AAA-3D110231E7EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {023B21F8-09EC-4A67-8AAA-3D110231E7EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {023B21F8-09EC-4A67-8AAA-3D110231E7EB}.Release|Any CPU.Build.0 = Release|Any CPU + {6DBBFF7B-2C28-47D7-8618-B6085044E38D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6DBBFF7B-2C28-47D7-8618-B6085044E38D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6DBBFF7B-2C28-47D7-8618-B6085044E38D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6DBBFF7B-2C28-47D7-8618-B6085044E38D}.Release|Any CPU.Build.0 = Release|Any CPU + {11B1F856-9025-4A4C-B90D-B1237743B672}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11B1F856-9025-4A4C-B90D-B1237743B672}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11B1F856-9025-4A4C-B90D-B1237743B672}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11B1F856-9025-4A4C-B90D-B1237743B672}.Release|Any CPU.Build.0 = Release|Any CPU + {05A91267-ABC8-4249-9A04-166C08EAD849}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {05A91267-ABC8-4249-9A04-166C08EAD849}.Debug|Any CPU.Build.0 = Debug|Any CPU + {05A91267-ABC8-4249-9A04-166C08EAD849}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05A91267-ABC8-4249-9A04-166C08EAD849}.Release|Any CPU.Build.0 = Release|Any CPU + {99D7BB0C-DE8D-4952-A9BF-63A5215256C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99D7BB0C-DE8D-4952-A9BF-63A5215256C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99D7BB0C-DE8D-4952-A9BF-63A5215256C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99D7BB0C-DE8D-4952-A9BF-63A5215256C4}.Release|Any CPU.Build.0 = Release|Any CPU + {31886751-5615-435C-A40C-EA2CC415BBDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {31886751-5615-435C-A40C-EA2CC415BBDD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31886751-5615-435C-A40C-EA2CC415BBDD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {31886751-5615-435C-A40C-EA2CC415BBDD}.Release|Any CPU.Build.0 = Release|Any CPU + {92558FFF-0935-4B12-8ED6-E3DC4E486B7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92558FFF-0935-4B12-8ED6-E3DC4E486B7F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92558FFF-0935-4B12-8ED6-E3DC4E486B7F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92558FFF-0935-4B12-8ED6-E3DC4E486B7F}.Release|Any CPU.Build.0 = Release|Any CPU + {5BC27DBC-6727-4DA1-B1AF-5EA5D3803627}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5BC27DBC-6727-4DA1-B1AF-5EA5D3803627}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5BC27DBC-6727-4DA1-B1AF-5EA5D3803627}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5BC27DBC-6727-4DA1-B1AF-5EA5D3803627}.Release|Any CPU.Build.0 = Release|Any CPU + {EC96BD3F-2DFE-4072-86F5-BFB349FF77D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC96BD3F-2DFE-4072-86F5-BFB349FF77D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC96BD3F-2DFE-4072-86F5-BFB349FF77D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC96BD3F-2DFE-4072-86F5-BFB349FF77D9}.Release|Any CPU.Build.0 = Release|Any CPU + {74F9C0F1-2E95-4EC8-B395-EB73AC4DD42B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {74F9C0F1-2E95-4EC8-B395-EB73AC4DD42B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {74F9C0F1-2E95-4EC8-B395-EB73AC4DD42B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {74F9C0F1-2E95-4EC8-B395-EB73AC4DD42B}.Release|Any CPU.Build.0 = Release|Any CPU + {A7D3307A-0A0D-4D20-887A-9C830A38B058}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A7D3307A-0A0D-4D20-887A-9C830A38B058}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A7D3307A-0A0D-4D20-887A-9C830A38B058}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A7D3307A-0A0D-4D20-887A-9C830A38B058}.Release|Any CPU.Build.0 = Release|Any CPU + {4B158F47-4759-495B-83BB-1D87130E3DF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4B158F47-4759-495B-83BB-1D87130E3DF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4B158F47-4759-495B-83BB-1D87130E3DF8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4B158F47-4759-495B-83BB-1D87130E3DF8}.Release|Any CPU.Build.0 = Release|Any CPU + {68CAEBF3-D428-4B50-8305-4BE4F7753CA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68CAEBF3-D428-4B50-8305-4BE4F7753CA8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68CAEBF3-D428-4B50-8305-4BE4F7753CA8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68CAEBF3-D428-4B50-8305-4BE4F7753CA8}.Release|Any CPU.Build.0 = Release|Any CPU + {0E75580E-10E6-4CAC-87C2-D83C6C0B1D10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E75580E-10E6-4CAC-87C2-D83C6C0B1D10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E75580E-10E6-4CAC-87C2-D83C6C0B1D10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E75580E-10E6-4CAC-87C2-D83C6C0B1D10}.Release|Any CPU.Build.0 = Release|Any CPU + {4E02E29B-54C7-4576-8575-0438EABCCC88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4E02E29B-54C7-4576-8575-0438EABCCC88}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E02E29B-54C7-4576-8575-0438EABCCC88}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4E02E29B-54C7-4576-8575-0438EABCCC88}.Release|Any CPU.Build.0 = Release|Any CPU + {485C6D8A-271F-44CD-9F55-B5283A917A00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {485C6D8A-271F-44CD-9F55-B5283A917A00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {485C6D8A-271F-44CD-9F55-B5283A917A00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {485C6D8A-271F-44CD-9F55-B5283A917A00}.Release|Any CPU.Build.0 = Release|Any CPU + {074F9FC2-B0E0-40E4-B356-99F0E07E279B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {074F9FC2-B0E0-40E4-B356-99F0E07E279B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {074F9FC2-B0E0-40E4-B356-99F0E07E279B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {074F9FC2-B0E0-40E4-B356-99F0E07E279B}.Release|Any CPU.Build.0 = Release|Any CPU + {ED7BCD8E-F553-48F0-962F-019BE7C2B78E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED7BCD8E-F553-48F0-962F-019BE7C2B78E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED7BCD8E-F553-48F0-962F-019BE7C2B78E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED7BCD8E-F553-48F0-962F-019BE7C2B78E}.Release|Any CPU.Build.0 = Release|Any CPU + {7D9C0EF5-7383-4E35-811B-3288B3C806F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D9C0EF5-7383-4E35-811B-3288B3C806F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D9C0EF5-7383-4E35-811B-3288B3C806F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D9C0EF5-7383-4E35-811B-3288B3C806F3}.Release|Any CPU.Build.0 = Release|Any CPU + {7C3FE60E-055B-4E0C-BB85-C7E94A640074}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C3FE60E-055B-4E0C-BB85-C7E94A640074}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C3FE60E-055B-4E0C-BB85-C7E94A640074}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C3FE60E-055B-4E0C-BB85-C7E94A640074}.Release|Any CPU.Build.0 = Release|Any CPU + {92BD1781-5DB4-4F72-BCCB-0D64C0790A2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92BD1781-5DB4-4F72-BCCB-0D64C0790A2B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92BD1781-5DB4-4F72-BCCB-0D64C0790A2B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92BD1781-5DB4-4F72-BCCB-0D64C0790A2B}.Release|Any CPU.Build.0 = Release|Any CPU + {421DE59C-8246-4679-9D69-79F16A7187BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {421DE59C-8246-4679-9D69-79F16A7187BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {421DE59C-8246-4679-9D69-79F16A7187BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {421DE59C-8246-4679-9D69-79F16A7187BE}.Release|Any CPU.Build.0 = Release|Any CPU + {AB6B3C69-9F6F-461C-BFD8-D3F25B9F44AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB6B3C69-9F6F-461C-BFD8-D3F25B9F44AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB6B3C69-9F6F-461C-BFD8-D3F25B9F44AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB6B3C69-9F6F-461C-BFD8-D3F25B9F44AD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {3B6ED9E7-C19E-4501-AAE6-9BE4EA6C18D7} = {65AB9A87-CEB0-4CC1-BFFE-FBC393C4DA75} - {A098CF55-9DE1-4C92-ADEC-931CFBFA8536} = {65AB9A87-CEB0-4CC1-BFFE-FBC393C4DA75} - {E268163A-17EB-4C14-A892-868ADFB3108E} = {65AB9A87-CEB0-4CC1-BFFE-FBC393C4DA75} - {65AB9A87-CEB0-4CC1-BFFE-FBC393C4DA75} = {DC4EAEF7-A2C9-4628-85FA-809F7287E234} - {E31F654A-1D4C-4898-8D3C-7A487F82317E} = {AB77CD09-4C32-4F58-8F07-915F8B050520} - {44E6D9DB-89FD-41C8-BAEB-5B96AA7793F6} = {AB77CD09-4C32-4F58-8F07-915F8B050520} - {ECFF0F53-89D5-4D7D-95D0-EAC1514D84E8} = {DC4EAEF7-A2C9-4628-85FA-809F7287E234} + {F9B73995-76C6-4056-ADA9-18342F951361} = {924F7971-780C-4E70-A306-86482469502E} + {BA2F4AB0-0F9B-48EE-B854-54C38CB91115} = {F9B73995-76C6-4056-ADA9-18342F951361} + {D060F449-05B9-4830-88A7-4CB879F23A77} = {F9B73995-76C6-4056-ADA9-18342F951361} + {8D8DB1B3-0533-4D14-B079-CD2EE922B3F8} = {F9B73995-76C6-4056-ADA9-18342F951361} + {DF972A62-6E0A-4354-B217-C39732B237F9} = {8D8DB1B3-0533-4D14-B079-CD2EE922B3F8} + {DE3A48A7-5368-468A-8EB3-F5E1A3A15EFF} = {8D8DB1B3-0533-4D14-B079-CD2EE922B3F8} + {BF3477AA-22C9-4751-A4C0-01EF30728AE9} = {8D8DB1B3-0533-4D14-B079-CD2EE922B3F8} + {A3E08CE3-2358-4D36-875B-82C99AC61CD8} = {924F7971-780C-4E70-A306-86482469502E} + {36F8BB88-BBDB-4917-BCF4-269E7652A314} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {F2892BD1-6FB9-4346-89A2-EC4C3076FC9E} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {F8785C36-69E1-423B-9E4A-36DF8A9CF904} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {2B77DAF6-67B2-47A0-91DC-94383C46C355} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {14C162CB-CD1D-4122-B123-D0A3AA91BEA8} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {87CFFE11-B7A1-4F08-971A-2D1F6A599E29} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {7B7D8972-B167-4338-B112-2BB37DBDBF71} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {96A81378-0E48-497E-A712-92114D177691} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {C429944E-EC27-42BD-8687-2C126B76861B} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {8E81D799-F8BF-443E-A25B-CEC06B4BA05E} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {F0E3E515-F604-4EE5-A013-6648F39322E4} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {22E65BCC-B91F-4CD5-AC5B-CB71AEF8ACDC} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {19D21A12-B2B6-4A99-8A57-C5E2C3C56192} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {0FF93648-F249-4F25-AF46-15C96168682B} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {7A28B74F-B3AC-43B9-B2E3-581316DCE809} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {A42D58EA-6C03-401C-B3E8-9671DD66FAB4} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {6E5011E7-13B3-4A74-863D-35356229616A} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {D130C11C-6E09-4D2D-BFB7-C40545E26C1B} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {ED49E6AD-3E1D-48FB-9DEF-FB8B53DA7179} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {9ECFBBD5-F7DD-4ECF-A738-41E2F747D67A} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {F08AEF4C-3314-4A26-87D7-8A6F52482C24} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {9905147E-CC1F-42A0-BD27-05586C583DF7} = {8C343846-5F9F-4033-9B52-B44C61962449} + {FE26E824-EC4D-4CF9-9927-7E4B81502349} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {56490F48-5770-4EAB-81A3-AF915398871C} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {78102FBF-4418-42FE-ACBA-A23BB2706F0A} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {EBB5F8C1-5220-4D45-88AF-AC97D77AA14C} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {0DD2AF99-AAA6-448A-8A9C-16EE373AFDAB} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {948BC599-48DC-4E41-8B82-58E0363B0C82} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {5DE03057-D112-4D96-AB25-9B295405AFA7} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {D4769BE4-D7CF-4DF0-AE6D-19271AAA523A} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {64B8D716-2D65-4324-9F49-C400BFD44A27} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {644A619F-E11D-4EAF-9CB2-7C8D3D19D767} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {CB54BA8F-2484-4E94-9950-2B293928CD9B} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {3DECEADE-4167-49BB-97ED-9D67AC9621AF} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {E4A13773-25AC-4CBA-B853-156FF04A5F93} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {2B2A5675-EA12-4921-A075-5C8BCB10D02D} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {09F74B4D-8BC9-43D1-8B64-696D055E0B81} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {5D46448D-1D58-46CB-BA0F-04AD763AC40D} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {6028A9D4-7197-4361-A394-BADA39B061B3} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {7642812C-564D-40CD-985A-F53EC821CBE3} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {6BCBD409-3C28-450D-AA27-D4E663BFE902} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {065D8144-FE05-4980-9C90-E67F682770CE} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {2F77A172-1944-4D02-BB72-696FE30778E6} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {8E9B5FE5-6C6B-4CC2-816B-8CDA6F3D1297} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {995E5475-2032-4A23-A85B-F8400C02CAFE} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {E8C5489A-5EBA-4471-ADCA-1CE3F1268AE8} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {68DEB3AF-8572-45A9-9EED-E71B5F1F35CA} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {A4812432-D0FE-4946-BF92-38E60B49B88D} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {D307A5F5-5652-4AD9-B463-2C0798BEBF3F} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {A9DA4D84-BEB9-4387-93A0-FD4136426A43} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {585D7F21-C0B1-41C3-A8B5-01545FEFDF6C} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {F1565840-5AF1-4CCA-A7AF-DA8AED89CE3E} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {1FDA0F25-0050-4A45-B20F-FEC93EC1FB98} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {F3ACBF36-B31B-4C17-8DB0-931B03EEFFDF} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {85F1F599-330B-4C3D-839E-17D5E3587C16} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {F148144A-15F7-4920-B2B5-5182250AF6B3} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {D01D8667-B0F3-426D-BC4E-78789199F757} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {F8D2D0D9-513C-4EE2-871C-1494837DE696} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {7E65A7AA-B615-4C92-95E6-39C64933517D} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {19DB3193-B920-4C62-ADC1-5071AE989AA5} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {4617ED77-9564-4A06-8F9B-92E5C5523FE1} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {023B21F8-09EC-4A67-8AAA-3D110231E7EB} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {A3AF556C-276C-49BA-A9ED-E7D42FECAA46} = {8C343846-5F9F-4033-9B52-B44C61962449} + {6DBBFF7B-2C28-47D7-8618-B6085044E38D} = {A3AF556C-276C-49BA-A9ED-E7D42FECAA46} + {11B1F856-9025-4A4C-B90D-B1237743B672} = {A3AF556C-276C-49BA-A9ED-E7D42FECAA46} + {05A91267-ABC8-4249-9A04-166C08EAD849} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {99D7BB0C-DE8D-4952-A9BF-63A5215256C4} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {31886751-5615-435C-A40C-EA2CC415BBDD} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {92558FFF-0935-4B12-8ED6-E3DC4E486B7F} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {5BC27DBC-6727-4DA1-B1AF-5EA5D3803627} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {EC96BD3F-2DFE-4072-86F5-BFB349FF77D9} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {74F9C0F1-2E95-4EC8-B395-EB73AC4DD42B} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {A7D3307A-0A0D-4D20-887A-9C830A38B058} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {4B158F47-4759-495B-83BB-1D87130E3DF8} = {A3E08CE3-2358-4D36-875B-82C99AC61CD8} + {68CAEBF3-D428-4B50-8305-4BE4F7753CA8} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {0E75580E-10E6-4CAC-87C2-D83C6C0B1D10} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {4E02E29B-54C7-4576-8575-0438EABCCC88} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {485C6D8A-271F-44CD-9F55-B5283A917A00} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {074F9FC2-B0E0-40E4-B356-99F0E07E279B} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {ED7BCD8E-F553-48F0-962F-019BE7C2B78E} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {7D9C0EF5-7383-4E35-811B-3288B3C806F3} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {7C3FE60E-055B-4E0C-BB85-C7E94A640074} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {92BD1781-5DB4-4F72-BCCB-0D64C0790A2B} = {9905147E-CC1F-42A0-BD27-05586C583DF7} + {421DE59C-8246-4679-9D69-79F16A7187BE} = {F9B73995-76C6-4056-ADA9-18342F951361} + {AB6B3C69-9F6F-461C-BFD8-D3F25B9F44AD} = {7A94E723-ADD6-48C4-BBE7-1D5B311187A8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {7075E20E-F8B5-460C-8CF0-132F617F386C} + SolutionGuid = {56695AA9-EA80-47A7-8562-E51285906C54} EndGlobalSection EndGlobal diff --git a/RoslynSDK.ruleset b/RoslynSDK.ruleset new file mode 100644 index 000000000..fc337d722 --- /dev/null +++ b/RoslynSDK.ruleset @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..27ff07f70 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,15 @@ +# Security Policy + +## Supported Versions + +The .NET Core and ASP.NET Core support policy, including supported versions can be found at the [.NET Core Support Policy Page](https://dotnet.microsoft.com/platform/support/policy/dotnet-core). + +## Reporting a Vulnerability + +Security issues and bugs should be reported privately to the Microsoft Security Response Center (MSRC), either by emailing secure@microsoft.com or via the portal at https://msrc.microsoft.com. +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your +original message. Further information, including the MSRC PGP key, can be found in the [MSRC Report an Issue FAQ](https://www.microsoft.com/en-us/msrc/faqs-report-an-issue). + +Reports via MSRC may qualify for the .NET Core Bug Bounty. Details of the .NET Core Bug Bounty including terms and conditions are at [https://aka.ms/corebounty](https://aka.ms/corebounty). + +Please do not open issues for anything you think might have a security implication. \ No newline at end of file diff --git a/Samples.sln b/Samples.sln new file mode 100644 index 000000000..71d51bee6 --- /dev/null +++ b/Samples.sln @@ -0,0 +1,366 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28606.18 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CSharp", "CSharp", "{C3FB27E9-C8EE-4F76-B0AA-7CD67A7E652B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Analyzers", "Analyzers", "{011210AA-E610-412F-8429-9A0A1A535F1D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MakeConst", "MakeConst", "{6D60DBCE-E362-489E-B78F-9DABAF03F5FD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ConvertToAutoProperty", "ConvertToAutoProperty", "{2BF6DA5F-ECCB-4DD3-936D-AF491EA0737E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A54A1AB7-DBD6-4C31-A22E-C53674137C53}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ConvertToConditional", "ConvertToConditional", "{20698A66-3CA3-4400-879F-76604C97D6EA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RefOutModifier", "RefOutModifier", "{880D64DA-1F67-4406-A86A-B97464B0380E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CSharpToVisualBasicConverter", "CSharpToVisualBasicConverter", "{D7541FAE-D6D7-41AD-B0D6-A83A1B8A628D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VisualBasic", "VisualBasic", "{CDA94F62-E35A-4913-8045-D9D42416513C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Analyzers", "Analyzers", "{FDCF8D27-D24F-41BB-8A21-22D83D062E9F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Analyzers.CSharp", "samples\CSharp\Analyzers\Analyzers.Implementation\Analyzers.CSharp.csproj", "{2DDE4D5E-9CB8-4E01-ABAA-D39DAA018A16}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Analyzers.CSharp.UnitTests", "samples\CSharp\Analyzers\Analyzers.Test\Analyzers.CSharp.UnitTests.csproj", "{0E5363B6-DE8C-42B5-9FD3-A2F02CB67001}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Analyzers.CSharp.Vsix", "samples\CSharp\Analyzers\Analyzers.Vsix\Analyzers.CSharp.Vsix.csproj", "{D3C8EE30-8885-43F4-9C76-0D66A13FC4EA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConvertToAutoProperty.CSharp", "samples\CSharp\ConvertToAutoProperty\ConvertToAutoProperty.Implementation\ConvertToAutoProperty.CSharp.csproj", "{827CDC89-B2BE-41E1-ABC2-792BA5B3A507}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConvertToAutoProperty.CSharp.Vsix", "samples\CSharp\ConvertToAutoProperty\ConvertToAutoProperty.Vsix\ConvertToAutoProperty.CSharp.Vsix.csproj", "{9DBD9E20-1C7D-4105-85CF-1F21660E1DE9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConvertToConditional.CSharp", "samples\CSharp\ConvertToConditional\ConvertToConditional.Implementation\ConvertToConditional.CSharp.csproj", "{1305FEEC-7DED-46A9-89C2-2544E8BECEA3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConvertToConditional.CSharp.UnitTests", "samples\CSharp\ConvertToConditional\ConvertToConditional.Test\ConvertToConditional.CSharp.UnitTests.csproj", "{2B6860EC-C15D-4E56-A5A2-6399F502B4FA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConvertToConditional.CSharp.Vsix", "samples\CSharp\ConvertToConditional\ConvertToConditional.Vsix\ConvertToConditional.CSharp.Vsix.csproj", "{D49B7CE5-5292-4EEA-AE1E-5B55C5E059D5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MakeConst.CSharp", "samples\CSharp\MakeConst\MakeConst.Implementation\MakeConst.CSharp.csproj", "{80612064-35D2-4FFD-ABCC-2953A84A825F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MakeConst.CSharp.Vsix", "samples\CSharp\MakeConst\MakeConst.Vsix\MakeConst.CSharp.Vsix.csproj", "{683D55F6-A035-48A5-B989-9B549DB7F53D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RefOutModifier.CSharp", "samples\CSharp\RefOutModifier\RefOutModifier.Implementation\RefOutModifier.CSharp.csproj", "{E2040FD9-5CF1-4500-9D55-D8994F1EF328}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RefOutModifier.CSharp.Vsix", "samples\CSharp\RefOutModifier\RefOutModifier.Vsix\RefOutModifier.CSharp.Vsix.csproj", "{DE024511-D4E4-4C3D-89DC-37C3A8AA8198}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "APISamples.CSharp.UnitTests", "samples\CSharp\APISamples\APISamples.CSharp.UnitTests.csproj", "{2A953FCA-A243-4013-85E5-1CF6B1677BB2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleClassifier.CSharp", "samples\CSharp\ConsoleClassifier\ConsoleClassifier.CSharp.csproj", "{DD8B331A-75C8-4F49-88A5-541D6FF88BFF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FormatSolution.CSharp", "samples\CSharp\FormatSolution\FormatSolution.CSharp.csproj", "{AED8172B-C125-46E9-AA69-A8F18C7914A0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TreeTransforms.CSharp.UnitTests", "samples\CSharp\TreeTransforms\TreeTransforms.CSharp.UnitTests.csproj", "{530E151F-3B74-4A9A-896E-79B56E10913B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roslyn.UnitTestFramework", "samples\Shared\UnitTestFramework\Roslyn.UnitTestFramework.csproj", "{0E033582-88DD-4BC6-A3C5-5B68519FB230}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Analyzers.VisualBasic", "samples\VisualBasic\Analyzers\Analyzers.Implementation\Analyzers.VisualBasic.vbproj", "{5AE04F72-8546-4CFD-8420-A30201B29017}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Analyzers.VisualBasic.Vsix", "samples\VisualBasic\Analyzers\Analyzers.Vsix\Analyzers.VisualBasic.Vsix.vbproj", "{7F1EE22F-ABE3-46A7-BA81-DE9E778D9BE4}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "APISamples.VisualBasic.UnitTests", "samples\VisualBasic\APISamples\APISamples.VisualBasic.UnitTests.vbproj", "{EF7EAD2E-6B2A-46AB-B788-47D3E868E553}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharpToVisualBasicConverter", "samples\CSharp\CSharpToVisualBasicConverter\CSharpToVisualBasicConverter.Lib\CSharpToVisualBasicConverter.csproj", "{404161BB-90BF-4DE0-916E-6AC37F45B4E1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharpToVisualBasicConverter.UnitTests", "samples\CSharp\CSharpToVisualBasicConverter\CSharpToVisualBasicConverter.Test\CSharpToVisualBasicConverter.UnitTests.csproj", "{91FCE068-24D9-4452-9629-40420206620C}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "ConsoleClassifier.VisualBasic", "samples\VisualBasic\ConsoleClassifier\ConsoleClassifier.VisualBasic.vbproj", "{AE1C0659-7CBF-4DD1-BBFE-D7A2273822E1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ConvertToAutoProperty", "ConvertToAutoProperty", "{D4494BA9-BD06-4A1D-95B2-A4E10724593F}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "ConvertToAutoProperty.VisualBasic", "samples\VisualBasic\ConvertToAutoProperty\ConvertToAutoProperty\ConvertToAutoProperty.VisualBasic.vbproj", "{C7811972-6954-4E90-83AC-6365DDA519B4}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "ConvertToAutoProperty.VisualBasic.Vsix", "samples\VisualBasic\ConvertToAutoProperty\ConvertToAutoProperty.Vsix\ConvertToAutoProperty.VisualBasic.Vsix.vbproj", "{B090C3E7-7DA0-4CDB-AC9E-903BDCB20DB8}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "FormatSolution.VisualBasic", "samples\VisualBasic\FormatSolution\FormatSolution.VisualBasic.vbproj", "{AE4D8D13-B58C-4B0F-B326-13D002505E58}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ImplementNotifyPropertyChanged", "ImplementNotifyPropertyChanged", "{01759F21-28C9-4934-A4DC-B9948C9DB803}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "ImplementNotifyPropertyChanged.VisualBasic", "samples\VisualBasic\ImplementNotifyPropertyChanged\ImplementNotifyPropertyChanged\ImplementNotifyPropertyChanged.VisualBasic.vbproj", "{22D1F704-48D2-495B-8ECD-43DA4F615211}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "ImplementNotifyPropertyChanged.VisualBasic.Vsix", "samples\VisualBasic\ImplementNotifyPropertyChanged\ImplementNotifyPropertyChanged.Vsix\ImplementNotifyPropertyChanged.VisualBasic.Vsix.vbproj", "{4C700A07-C81B-44D5-8C9F-BD874775C57B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ImplementNotifyPropertyChanged", "ImplementNotifyPropertyChanged", "{01998C95-7806-4BED-811D-70D4F8B1E112}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImplementNotifyPropertyChanged.CSharp", "samples\CSharp\ImplementNotifyPropertyChanged\ImplementNotifyPropertyChanged\ImplementNotifyPropertyChanged.CSharp.csproj", "{7F30B89A-FFC5-4645-8273-FF12BCC2BD04}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImplementNotifyPropertyChanged.CSharp.Vsix", "samples\CSharp\ImplementNotifyPropertyChanged\ImplementNotifyPropertyChanged.Vsix\ImplementNotifyPropertyChanged.CSharp.Vsix.csproj", "{1396AC51-E385-4894-A354-9D566189DDEB}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "TreeTransforms.VisualBasic.UnitTests", "samples\VisualBasic\TreeTransforms\TreeTransforms.VisualBasic.UnitTests.vbproj", "{996E8EDA-D925-45BB-B64A-079381A2BC7A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RemoveByVal", "RemoveByVal", "{64885FF2-EE63-4433-A890-7903B9E8AFBC}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "RemoveByVal.VisualBasic", "samples\VisualBasic\RemoveByVal\RemoveByVal\RemoveByVal.VisualBasic.vbproj", "{54FEC454-DC25-4700-B315-E576F1860C41}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "RemoveByVal.VisualBasic.Vsix", "samples\VisualBasic\RemoveByVal\RemoveByVal.Vsix\RemoveByVal.VisualBasic.Vsix.vbproj", "{BD5C5824-470E-4451-BC21-5E50892905E9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MakeConst", "MakeConst", "{8E328BEA-C8D7-4F4B-A357-4A84CAF76EDE}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "MakeConst.VisualBasic", "samples\VisualBasic\MakeConst\MakeConst\MakeConst.VisualBasic.vbproj", "{AB0EC03A-DC42-448D-BECA-C764DC6461EE}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "MakeConst.VisualBasic.UnitTests", "samples\VisualBasic\MakeConst\MakeConst.Test\MakeConst.VisualBasic.UnitTests.vbproj", "{20E6E463-CC7E-4A74-98BF-DF956D651360}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "MakeConst.VisualBasic.Vsix", "samples\VisualBasic\MakeConst\MakeConst.Vsix\MakeConst.VisualBasic.Vsix.vbproj", "{9C74F8EA-E6C7-448C-BC04-29F4CE9DC994}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VisualBasicToCSharpConverter", "VisualBasicToCSharpConverter", "{8E1C9AEC-6EF1-43A8-A378-52C5C0E40532}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "VisualBasicToCSharpConverter.Lib", "samples\VisualBasic\VisualBasicToCSharpConverter\VisualBasicToCSharpConverter.Lib\VisualBasicToCSharpConverter.Lib.vbproj", "{ECB83742-8023-4609-B139-D7B78DD66ED9}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "VisualBasicToCSharpConverter.UnitTests", "samples\VisualBasic\VisualBasicToCSharpConverter\VisualBasicToCSharpConverter.Test\VisualBasicToCSharpConverter.UnitTests.vbproj", "{5B7D7569-B5EE-4C01-9AFA-BC1958588160}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SourceGenerators", "SourceGenerators", "{14D18F51-6B59-49D5-9AB7-08B38417A459}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharpSourceGeneratorSamples", "samples\CSharp\SourceGenerators\SourceGeneratorSamples\CSharpSourceGeneratorSamples.csproj", "{2ADE5CFA-5DF4-44A9-BD67-E884BCFBA045}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharpGeneratedDemo", "samples\CSharp\SourceGenerators\GeneratedDemo\CSharpGeneratedDemo.csproj", "{EC4DB63B-C2B4-4D06-AF98-15253035C6D5}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "VisualBasicGeneratedDemo", "samples\VisualBasic\SourceGenerators\GeneratedDemo\VisualBasicGeneratedDemo.vbproj", "{DA924876-9CF5-47E0-AA01-ADAF47653D39}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "VisualBasicSourceGeneratorSamples", "samples\VisualBasic\SourceGenerators\SourceGeneratorSamples\VisualBasicSourceGeneratorSamples.vbproj", "{8322B6E4-0CB1-4EC1-A2CC-2E4DB02C834A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SourceGenerators", "SourceGenerators", "{E79B07C8-0859-4B5C-9650-68D855833C6E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2DDE4D5E-9CB8-4E01-ABAA-D39DAA018A16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DDE4D5E-9CB8-4E01-ABAA-D39DAA018A16}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DDE4D5E-9CB8-4E01-ABAA-D39DAA018A16}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DDE4D5E-9CB8-4E01-ABAA-D39DAA018A16}.Release|Any CPU.Build.0 = Release|Any CPU + {0E5363B6-DE8C-42B5-9FD3-A2F02CB67001}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E5363B6-DE8C-42B5-9FD3-A2F02CB67001}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E5363B6-DE8C-42B5-9FD3-A2F02CB67001}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E5363B6-DE8C-42B5-9FD3-A2F02CB67001}.Release|Any CPU.Build.0 = Release|Any CPU + {D3C8EE30-8885-43F4-9C76-0D66A13FC4EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3C8EE30-8885-43F4-9C76-0D66A13FC4EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3C8EE30-8885-43F4-9C76-0D66A13FC4EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3C8EE30-8885-43F4-9C76-0D66A13FC4EA}.Release|Any CPU.Build.0 = Release|Any CPU + {827CDC89-B2BE-41E1-ABC2-792BA5B3A507}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {827CDC89-B2BE-41E1-ABC2-792BA5B3A507}.Debug|Any CPU.Build.0 = Debug|Any CPU + {827CDC89-B2BE-41E1-ABC2-792BA5B3A507}.Release|Any CPU.ActiveCfg = Release|Any CPU + {827CDC89-B2BE-41E1-ABC2-792BA5B3A507}.Release|Any CPU.Build.0 = Release|Any CPU + {9DBD9E20-1C7D-4105-85CF-1F21660E1DE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9DBD9E20-1C7D-4105-85CF-1F21660E1DE9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9DBD9E20-1C7D-4105-85CF-1F21660E1DE9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9DBD9E20-1C7D-4105-85CF-1F21660E1DE9}.Release|Any CPU.Build.0 = Release|Any CPU + {1305FEEC-7DED-46A9-89C2-2544E8BECEA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1305FEEC-7DED-46A9-89C2-2544E8BECEA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1305FEEC-7DED-46A9-89C2-2544E8BECEA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1305FEEC-7DED-46A9-89C2-2544E8BECEA3}.Release|Any CPU.Build.0 = Release|Any CPU + {2B6860EC-C15D-4E56-A5A2-6399F502B4FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B6860EC-C15D-4E56-A5A2-6399F502B4FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B6860EC-C15D-4E56-A5A2-6399F502B4FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B6860EC-C15D-4E56-A5A2-6399F502B4FA}.Release|Any CPU.Build.0 = Release|Any CPU + {D49B7CE5-5292-4EEA-AE1E-5B55C5E059D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D49B7CE5-5292-4EEA-AE1E-5B55C5E059D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D49B7CE5-5292-4EEA-AE1E-5B55C5E059D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D49B7CE5-5292-4EEA-AE1E-5B55C5E059D5}.Release|Any CPU.Build.0 = Release|Any CPU + {80612064-35D2-4FFD-ABCC-2953A84A825F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {80612064-35D2-4FFD-ABCC-2953A84A825F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80612064-35D2-4FFD-ABCC-2953A84A825F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80612064-35D2-4FFD-ABCC-2953A84A825F}.Release|Any CPU.Build.0 = Release|Any CPU + {683D55F6-A035-48A5-B989-9B549DB7F53D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {683D55F6-A035-48A5-B989-9B549DB7F53D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {683D55F6-A035-48A5-B989-9B549DB7F53D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {683D55F6-A035-48A5-B989-9B549DB7F53D}.Release|Any CPU.Build.0 = Release|Any CPU + {E2040FD9-5CF1-4500-9D55-D8994F1EF328}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2040FD9-5CF1-4500-9D55-D8994F1EF328}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2040FD9-5CF1-4500-9D55-D8994F1EF328}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2040FD9-5CF1-4500-9D55-D8994F1EF328}.Release|Any CPU.Build.0 = Release|Any CPU + {DE024511-D4E4-4C3D-89DC-37C3A8AA8198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE024511-D4E4-4C3D-89DC-37C3A8AA8198}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE024511-D4E4-4C3D-89DC-37C3A8AA8198}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE024511-D4E4-4C3D-89DC-37C3A8AA8198}.Release|Any CPU.Build.0 = Release|Any CPU + {2A953FCA-A243-4013-85E5-1CF6B1677BB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A953FCA-A243-4013-85E5-1CF6B1677BB2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A953FCA-A243-4013-85E5-1CF6B1677BB2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A953FCA-A243-4013-85E5-1CF6B1677BB2}.Release|Any CPU.Build.0 = Release|Any CPU + {DD8B331A-75C8-4F49-88A5-541D6FF88BFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD8B331A-75C8-4F49-88A5-541D6FF88BFF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD8B331A-75C8-4F49-88A5-541D6FF88BFF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD8B331A-75C8-4F49-88A5-541D6FF88BFF}.Release|Any CPU.Build.0 = Release|Any CPU + {AED8172B-C125-46E9-AA69-A8F18C7914A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AED8172B-C125-46E9-AA69-A8F18C7914A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AED8172B-C125-46E9-AA69-A8F18C7914A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AED8172B-C125-46E9-AA69-A8F18C7914A0}.Release|Any CPU.Build.0 = Release|Any CPU + {530E151F-3B74-4A9A-896E-79B56E10913B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {530E151F-3B74-4A9A-896E-79B56E10913B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {530E151F-3B74-4A9A-896E-79B56E10913B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {530E151F-3B74-4A9A-896E-79B56E10913B}.Release|Any CPU.Build.0 = Release|Any CPU + {0E033582-88DD-4BC6-A3C5-5B68519FB230}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E033582-88DD-4BC6-A3C5-5B68519FB230}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E033582-88DD-4BC6-A3C5-5B68519FB230}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E033582-88DD-4BC6-A3C5-5B68519FB230}.Release|Any CPU.Build.0 = Release|Any CPU + {5AE04F72-8546-4CFD-8420-A30201B29017}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5AE04F72-8546-4CFD-8420-A30201B29017}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5AE04F72-8546-4CFD-8420-A30201B29017}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5AE04F72-8546-4CFD-8420-A30201B29017}.Release|Any CPU.Build.0 = Release|Any CPU + {7F1EE22F-ABE3-46A7-BA81-DE9E778D9BE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F1EE22F-ABE3-46A7-BA81-DE9E778D9BE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F1EE22F-ABE3-46A7-BA81-DE9E778D9BE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F1EE22F-ABE3-46A7-BA81-DE9E778D9BE4}.Release|Any CPU.Build.0 = Release|Any CPU + {EF7EAD2E-6B2A-46AB-B788-47D3E868E553}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF7EAD2E-6B2A-46AB-B788-47D3E868E553}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF7EAD2E-6B2A-46AB-B788-47D3E868E553}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF7EAD2E-6B2A-46AB-B788-47D3E868E553}.Release|Any CPU.Build.0 = Release|Any CPU + {404161BB-90BF-4DE0-916E-6AC37F45B4E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {404161BB-90BF-4DE0-916E-6AC37F45B4E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {404161BB-90BF-4DE0-916E-6AC37F45B4E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {404161BB-90BF-4DE0-916E-6AC37F45B4E1}.Release|Any CPU.Build.0 = Release|Any CPU + {91FCE068-24D9-4452-9629-40420206620C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {91FCE068-24D9-4452-9629-40420206620C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91FCE068-24D9-4452-9629-40420206620C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {91FCE068-24D9-4452-9629-40420206620C}.Release|Any CPU.Build.0 = Release|Any CPU + {AE1C0659-7CBF-4DD1-BBFE-D7A2273822E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE1C0659-7CBF-4DD1-BBFE-D7A2273822E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE1C0659-7CBF-4DD1-BBFE-D7A2273822E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE1C0659-7CBF-4DD1-BBFE-D7A2273822E1}.Release|Any CPU.Build.0 = Release|Any CPU + {C7811972-6954-4E90-83AC-6365DDA519B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7811972-6954-4E90-83AC-6365DDA519B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7811972-6954-4E90-83AC-6365DDA519B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7811972-6954-4E90-83AC-6365DDA519B4}.Release|Any CPU.Build.0 = Release|Any CPU + {B090C3E7-7DA0-4CDB-AC9E-903BDCB20DB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B090C3E7-7DA0-4CDB-AC9E-903BDCB20DB8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B090C3E7-7DA0-4CDB-AC9E-903BDCB20DB8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B090C3E7-7DA0-4CDB-AC9E-903BDCB20DB8}.Release|Any CPU.Build.0 = Release|Any CPU + {AE4D8D13-B58C-4B0F-B326-13D002505E58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE4D8D13-B58C-4B0F-B326-13D002505E58}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE4D8D13-B58C-4B0F-B326-13D002505E58}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE4D8D13-B58C-4B0F-B326-13D002505E58}.Release|Any CPU.Build.0 = Release|Any CPU + {22D1F704-48D2-495B-8ECD-43DA4F615211}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22D1F704-48D2-495B-8ECD-43DA4F615211}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22D1F704-48D2-495B-8ECD-43DA4F615211}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22D1F704-48D2-495B-8ECD-43DA4F615211}.Release|Any CPU.Build.0 = Release|Any CPU + {4C700A07-C81B-44D5-8C9F-BD874775C57B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C700A07-C81B-44D5-8C9F-BD874775C57B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C700A07-C81B-44D5-8C9F-BD874775C57B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C700A07-C81B-44D5-8C9F-BD874775C57B}.Release|Any CPU.Build.0 = Release|Any CPU + {7F30B89A-FFC5-4645-8273-FF12BCC2BD04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F30B89A-FFC5-4645-8273-FF12BCC2BD04}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F30B89A-FFC5-4645-8273-FF12BCC2BD04}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F30B89A-FFC5-4645-8273-FF12BCC2BD04}.Release|Any CPU.Build.0 = Release|Any CPU + {1396AC51-E385-4894-A354-9D566189DDEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1396AC51-E385-4894-A354-9D566189DDEB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1396AC51-E385-4894-A354-9D566189DDEB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1396AC51-E385-4894-A354-9D566189DDEB}.Release|Any CPU.Build.0 = Release|Any CPU + {996E8EDA-D925-45BB-B64A-079381A2BC7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {996E8EDA-D925-45BB-B64A-079381A2BC7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {996E8EDA-D925-45BB-B64A-079381A2BC7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {996E8EDA-D925-45BB-B64A-079381A2BC7A}.Release|Any CPU.Build.0 = Release|Any CPU + {54FEC454-DC25-4700-B315-E576F1860C41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {54FEC454-DC25-4700-B315-E576F1860C41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {54FEC454-DC25-4700-B315-E576F1860C41}.Release|Any CPU.ActiveCfg = Release|Any CPU + {54FEC454-DC25-4700-B315-E576F1860C41}.Release|Any CPU.Build.0 = Release|Any CPU + {BD5C5824-470E-4451-BC21-5E50892905E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD5C5824-470E-4451-BC21-5E50892905E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD5C5824-470E-4451-BC21-5E50892905E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD5C5824-470E-4451-BC21-5E50892905E9}.Release|Any CPU.Build.0 = Release|Any CPU + {AB0EC03A-DC42-448D-BECA-C764DC6461EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB0EC03A-DC42-448D-BECA-C764DC6461EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB0EC03A-DC42-448D-BECA-C764DC6461EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB0EC03A-DC42-448D-BECA-C764DC6461EE}.Release|Any CPU.Build.0 = Release|Any CPU + {20E6E463-CC7E-4A74-98BF-DF956D651360}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {20E6E463-CC7E-4A74-98BF-DF956D651360}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20E6E463-CC7E-4A74-98BF-DF956D651360}.Release|Any CPU.ActiveCfg = Release|Any CPU + {20E6E463-CC7E-4A74-98BF-DF956D651360}.Release|Any CPU.Build.0 = Release|Any CPU + {9C74F8EA-E6C7-448C-BC04-29F4CE9DC994}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C74F8EA-E6C7-448C-BC04-29F4CE9DC994}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C74F8EA-E6C7-448C-BC04-29F4CE9DC994}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C74F8EA-E6C7-448C-BC04-29F4CE9DC994}.Release|Any CPU.Build.0 = Release|Any CPU + {ECB83742-8023-4609-B139-D7B78DD66ED9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ECB83742-8023-4609-B139-D7B78DD66ED9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ECB83742-8023-4609-B139-D7B78DD66ED9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ECB83742-8023-4609-B139-D7B78DD66ED9}.Release|Any CPU.Build.0 = Release|Any CPU + {5B7D7569-B5EE-4C01-9AFA-BC1958588160}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B7D7569-B5EE-4C01-9AFA-BC1958588160}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B7D7569-B5EE-4C01-9AFA-BC1958588160}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B7D7569-B5EE-4C01-9AFA-BC1958588160}.Release|Any CPU.Build.0 = Release|Any CPU + {2ADE5CFA-5DF4-44A9-BD67-E884BCFBA045}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2ADE5CFA-5DF4-44A9-BD67-E884BCFBA045}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2ADE5CFA-5DF4-44A9-BD67-E884BCFBA045}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2ADE5CFA-5DF4-44A9-BD67-E884BCFBA045}.Release|Any CPU.Build.0 = Release|Any CPU + {EC4DB63B-C2B4-4D06-AF98-15253035C6D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC4DB63B-C2B4-4D06-AF98-15253035C6D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC4DB63B-C2B4-4D06-AF98-15253035C6D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC4DB63B-C2B4-4D06-AF98-15253035C6D5}.Release|Any CPU.Build.0 = Release|Any CPU + {DA924876-9CF5-47E0-AA01-ADAF47653D39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA924876-9CF5-47E0-AA01-ADAF47653D39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA924876-9CF5-47E0-AA01-ADAF47653D39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA924876-9CF5-47E0-AA01-ADAF47653D39}.Release|Any CPU.Build.0 = Release|Any CPU + {8322B6E4-0CB1-4EC1-A2CC-2E4DB02C834A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8322B6E4-0CB1-4EC1-A2CC-2E4DB02C834A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8322B6E4-0CB1-4EC1-A2CC-2E4DB02C834A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8322B6E4-0CB1-4EC1-A2CC-2E4DB02C834A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {011210AA-E610-412F-8429-9A0A1A535F1D} = {C3FB27E9-C8EE-4F76-B0AA-7CD67A7E652B} + {6D60DBCE-E362-489E-B78F-9DABAF03F5FD} = {C3FB27E9-C8EE-4F76-B0AA-7CD67A7E652B} + {2BF6DA5F-ECCB-4DD3-936D-AF491EA0737E} = {C3FB27E9-C8EE-4F76-B0AA-7CD67A7E652B} + {20698A66-3CA3-4400-879F-76604C97D6EA} = {C3FB27E9-C8EE-4F76-B0AA-7CD67A7E652B} + {880D64DA-1F67-4406-A86A-B97464B0380E} = {C3FB27E9-C8EE-4F76-B0AA-7CD67A7E652B} + {D7541FAE-D6D7-41AD-B0D6-A83A1B8A628D} = {C3FB27E9-C8EE-4F76-B0AA-7CD67A7E652B} + {FDCF8D27-D24F-41BB-8A21-22D83D062E9F} = {CDA94F62-E35A-4913-8045-D9D42416513C} + {2DDE4D5E-9CB8-4E01-ABAA-D39DAA018A16} = {011210AA-E610-412F-8429-9A0A1A535F1D} + {0E5363B6-DE8C-42B5-9FD3-A2F02CB67001} = {011210AA-E610-412F-8429-9A0A1A535F1D} + {D3C8EE30-8885-43F4-9C76-0D66A13FC4EA} = {011210AA-E610-412F-8429-9A0A1A535F1D} + {827CDC89-B2BE-41E1-ABC2-792BA5B3A507} = {2BF6DA5F-ECCB-4DD3-936D-AF491EA0737E} + {9DBD9E20-1C7D-4105-85CF-1F21660E1DE9} = {2BF6DA5F-ECCB-4DD3-936D-AF491EA0737E} + {1305FEEC-7DED-46A9-89C2-2544E8BECEA3} = {20698A66-3CA3-4400-879F-76604C97D6EA} + {2B6860EC-C15D-4E56-A5A2-6399F502B4FA} = {20698A66-3CA3-4400-879F-76604C97D6EA} + {D49B7CE5-5292-4EEA-AE1E-5B55C5E059D5} = {20698A66-3CA3-4400-879F-76604C97D6EA} + {80612064-35D2-4FFD-ABCC-2953A84A825F} = {6D60DBCE-E362-489E-B78F-9DABAF03F5FD} + {683D55F6-A035-48A5-B989-9B549DB7F53D} = {6D60DBCE-E362-489E-B78F-9DABAF03F5FD} + {E2040FD9-5CF1-4500-9D55-D8994F1EF328} = {880D64DA-1F67-4406-A86A-B97464B0380E} + {DE024511-D4E4-4C3D-89DC-37C3A8AA8198} = {880D64DA-1F67-4406-A86A-B97464B0380E} + {2A953FCA-A243-4013-85E5-1CF6B1677BB2} = {C3FB27E9-C8EE-4F76-B0AA-7CD67A7E652B} + {DD8B331A-75C8-4F49-88A5-541D6FF88BFF} = {C3FB27E9-C8EE-4F76-B0AA-7CD67A7E652B} + {AED8172B-C125-46E9-AA69-A8F18C7914A0} = {C3FB27E9-C8EE-4F76-B0AA-7CD67A7E652B} + {530E151F-3B74-4A9A-896E-79B56E10913B} = {C3FB27E9-C8EE-4F76-B0AA-7CD67A7E652B} + {0E033582-88DD-4BC6-A3C5-5B68519FB230} = {A54A1AB7-DBD6-4C31-A22E-C53674137C53} + {5AE04F72-8546-4CFD-8420-A30201B29017} = {FDCF8D27-D24F-41BB-8A21-22D83D062E9F} + {7F1EE22F-ABE3-46A7-BA81-DE9E778D9BE4} = {FDCF8D27-D24F-41BB-8A21-22D83D062E9F} + {EF7EAD2E-6B2A-46AB-B788-47D3E868E553} = {CDA94F62-E35A-4913-8045-D9D42416513C} + {404161BB-90BF-4DE0-916E-6AC37F45B4E1} = {D7541FAE-D6D7-41AD-B0D6-A83A1B8A628D} + {91FCE068-24D9-4452-9629-40420206620C} = {D7541FAE-D6D7-41AD-B0D6-A83A1B8A628D} + {AE1C0659-7CBF-4DD1-BBFE-D7A2273822E1} = {CDA94F62-E35A-4913-8045-D9D42416513C} + {D4494BA9-BD06-4A1D-95B2-A4E10724593F} = {CDA94F62-E35A-4913-8045-D9D42416513C} + {C7811972-6954-4E90-83AC-6365DDA519B4} = {D4494BA9-BD06-4A1D-95B2-A4E10724593F} + {B090C3E7-7DA0-4CDB-AC9E-903BDCB20DB8} = {D4494BA9-BD06-4A1D-95B2-A4E10724593F} + {AE4D8D13-B58C-4B0F-B326-13D002505E58} = {CDA94F62-E35A-4913-8045-D9D42416513C} + {01759F21-28C9-4934-A4DC-B9948C9DB803} = {CDA94F62-E35A-4913-8045-D9D42416513C} + {22D1F704-48D2-495B-8ECD-43DA4F615211} = {01759F21-28C9-4934-A4DC-B9948C9DB803} + {4C700A07-C81B-44D5-8C9F-BD874775C57B} = {01759F21-28C9-4934-A4DC-B9948C9DB803} + {01998C95-7806-4BED-811D-70D4F8B1E112} = {C3FB27E9-C8EE-4F76-B0AA-7CD67A7E652B} + {7F30B89A-FFC5-4645-8273-FF12BCC2BD04} = {01998C95-7806-4BED-811D-70D4F8B1E112} + {1396AC51-E385-4894-A354-9D566189DDEB} = {01998C95-7806-4BED-811D-70D4F8B1E112} + {996E8EDA-D925-45BB-B64A-079381A2BC7A} = {CDA94F62-E35A-4913-8045-D9D42416513C} + {64885FF2-EE63-4433-A890-7903B9E8AFBC} = {CDA94F62-E35A-4913-8045-D9D42416513C} + {54FEC454-DC25-4700-B315-E576F1860C41} = {64885FF2-EE63-4433-A890-7903B9E8AFBC} + {BD5C5824-470E-4451-BC21-5E50892905E9} = {64885FF2-EE63-4433-A890-7903B9E8AFBC} + {8E328BEA-C8D7-4F4B-A357-4A84CAF76EDE} = {CDA94F62-E35A-4913-8045-D9D42416513C} + {AB0EC03A-DC42-448D-BECA-C764DC6461EE} = {8E328BEA-C8D7-4F4B-A357-4A84CAF76EDE} + {20E6E463-CC7E-4A74-98BF-DF956D651360} = {8E328BEA-C8D7-4F4B-A357-4A84CAF76EDE} + {9C74F8EA-E6C7-448C-BC04-29F4CE9DC994} = {8E328BEA-C8D7-4F4B-A357-4A84CAF76EDE} + {8E1C9AEC-6EF1-43A8-A378-52C5C0E40532} = {CDA94F62-E35A-4913-8045-D9D42416513C} + {ECB83742-8023-4609-B139-D7B78DD66ED9} = {8E1C9AEC-6EF1-43A8-A378-52C5C0E40532} + {5B7D7569-B5EE-4C01-9AFA-BC1958588160} = {8E1C9AEC-6EF1-43A8-A378-52C5C0E40532} + {14D18F51-6B59-49D5-9AB7-08B38417A459} = {C3FB27E9-C8EE-4F76-B0AA-7CD67A7E652B} + {2ADE5CFA-5DF4-44A9-BD67-E884BCFBA045} = {14D18F51-6B59-49D5-9AB7-08B38417A459} + {EC4DB63B-C2B4-4D06-AF98-15253035C6D5} = {14D18F51-6B59-49D5-9AB7-08B38417A459} + {DA924876-9CF5-47E0-AA01-ADAF47653D39} = {E79B07C8-0859-4B5C-9650-68D855833C6E} + {8322B6E4-0CB1-4EC1-A2CC-2E4DB02C834A} = {E79B07C8-0859-4B5C-9650-68D855833C6E} + {E79B07C8-0859-4B5C-9650-68D855833C6E} = {CDA94F62-E35A-4913-8045-D9D42416513C} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B849838B-3D7A-4B6B-BE07-285DCB1588F4} + EndGlobalSection +EndGlobal diff --git a/build.cmd b/build.cmd index 9262a57cd..ff7629949 100644 --- a/build.cmd +++ b/build.cmd @@ -1,3 +1,3 @@ @echo off -powershell -ExecutionPolicy ByPass %~dp0build\Build.ps1 -restore -build -deploy -log %* +powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0eng\common\Build.ps1""" -restore -build -pack %*" exit /b %ErrorLevel% \ No newline at end of file diff --git a/build/CIBuild.cmd b/build/CIBuild.cmd deleted file mode 100644 index dd4cd135c..000000000 --- a/build/CIBuild.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@echo off -powershell -ExecutionPolicy ByPass %~dp0Build.ps1 -restore -build -sign -pack -ci %* -exit /b %ErrorLevel% \ No newline at end of file diff --git a/build/Nuget.props b/build/Nuget.props deleted file mode 100644 index 128dea022..000000000 --- a/build/Nuget.props +++ /dev/null @@ -1,10 +0,0 @@ - - - - - $(NUGET_PACKAGES) - $(UserProfile)\.nuget\packages\ - $([System.Environment]::GetFolderPath(SpecialFolder.Personal))\.nuget\packages\ - $(NuGetPackageRoot)\ - - \ No newline at end of file diff --git a/build/SignToolData.json b/build/SignToolData.json deleted file mode 100644 index aae807b02..000000000 --- a/build/SignToolData.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "sign": [ - { - "certificate": "MicrosoftSHA2", - "strongName": "MsSharedLib72", - "values": [ - "bin/Templates/Templates.VisualStudio.2015/net461/Roslyn.SDK.Template.Wizard.dll", - "bin/Templates/Templates.VisualStudio.2015/net461/Roslyn.SyntaxVisualizer.Control.dll", - "bin/Templates/Templates.VisualStudio.2015/net461/Roslyn.SyntaxVisualizer.DgmlHelper.dll", - "bin/Templates/Templates.VisualStudio.2015/net461/Roslyn.SyntaxVisualizer.Extension.dll", - "bin/Templates/Templates.VisualStudio.2015/net461/*/Roslyn.SyntaxVisualizer.Extension.resources.dll", - "bin/Templates/Templates.VisualStudio.2017/net461/Roslyn.SDK.Template.Wizard.dll", - "bin/Templates/Templates.VisualStudio.2017/net461/Roslyn.SyntaxVisualizer.Control.dll", - "bin/Templates/Templates.VisualStudio.2017/net461/Roslyn.SyntaxVisualizer.DgmlHelper.dll", - "bin/Templates/Templates.VisualStudio.2017/net461/Roslyn.SyntaxVisualizer.Extension.dll", - "bin/Templates/Templates.VisualStudio.2017/net461/*/Roslyn.SyntaxVisualizer.Extension.resources.dll", - ] - }, - { - "certificate": "VsixSHA2", - "strongName": null, - "values": [ - "VSSetup/Roslyn.SDK.VS2015.vsix", - "VSSetup/Roslyn.SDK.VS2017.vsix", - "VSSetup/Insertion/Roslyn.SDK.VS2017.vsix", - ] - } - ] -} \ No newline at end of file diff --git a/build/Tasks/Directory.Build.props b/build/Tasks/Directory.Build.props deleted file mode 100644 index d32e65639..000000000 --- a/build/Tasks/Directory.Build.props +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/build/Tasks/Directory.Build.targets b/build/Tasks/Directory.Build.targets deleted file mode 100644 index dca3e5089..000000000 --- a/build/Tasks/Directory.Build.targets +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/build/Tasks/UpdateTemplateVersion/UpdateTemplateVersion.vb b/build/Tasks/UpdateTemplateVersion/UpdateTemplateVersion.vb deleted file mode 100644 index 4433e3ff2..000000000 --- a/build/Tasks/UpdateTemplateVersion/UpdateTemplateVersion.vb +++ /dev/null @@ -1,165 +0,0 @@ -Imports System.IO -Imports System.Text -Imports Microsoft.Build.Framework -Imports Microsoft.Build.Utilities -Imports - -Public Class UpdateTemplateVersion - Inherits Task - - - Public Property VSTemplatesToRewrite As ITaskItem() - - - Public Property AssemblyVersion As String - - - Public Property IntermediatePath As String - - Private ReadOnly _newVSTemplates As New List(Of ITaskItem) - - - Public ReadOnly Property NewVSTemplates As ITaskItem() - Get - Return _newVSTemplates.ToArray() - End Get - End Property - - Public Overrides Function Execute() As Boolean - Try - If Directory.Exists(IntermediatePath) Then - For Each file In New DirectoryInfo(IntermediatePath).GetFiles("*.*", SearchOption.AllDirectories) - file.IsReadOnly = False - Next - - Directory.Delete(IntermediatePath, recursive:=True) - End If - - For Each template In VSTemplatesToRewrite - Dim templateXml = UpdateAssemblyVersion(template.ItemSpec) - Dim newDirectory = SaveTemplateXml(templateXml, template.ItemSpec, IntermediatePath, template.GetMetadata("OutputSubPath"), _newVSTemplates) - ' For all other files, just copy them along - Dim originalDirectory = Path.GetDirectoryName(Path.GetFullPath(template.ItemSpec)) - For Each extraFile In New DirectoryInfo(originalDirectory).GetFiles("*.*", SearchOption.AllDirectories) - If extraFile.Extension = ".vstemplate" Then - ' If this is one of our multi-project template files we need to do a rename - If IsReferencedTemplate(template, extraFile) Then - Dim referencedTemplateXml = UpdateAssemblyVersion(extraFile.FullName) - Dim relativePath = GetRelativePath(Path.GetFullPath(newDirectory), extraFile.FullName).Replace("..\", String.Empty) - Dim fullPath = Path.GetFullPath(Path.Combine(newDirectory, relativePath)) - referencedTemplateXml.Save(fullPath) - Else - Continue For - End If - End If - CopyExtraFile(newDirectory, originalDirectory, extraFile.FullName) - Next - Next - - Return True - Catch ex As Exception - ' Show the stack trace in the build log to make diagnosis easier. - Log.LogMessage($"Exception was thrown, stack trace was: {Environment.NewLine}{ex.StackTrace}") - Throw - End Try - End Function - - Private Function UpdateAssemblyVersion(fullPath As String) As XDocument - Dim xml = XDocument.Load(fullPath) - - For Each assembly In xml... - assembly.Value = assembly.Value.Replace("$(AssemblyVersion)", AssemblyVersion) - Next - - Return xml - End Function - - Private Function SaveTemplateXml(xml As XDocument, templatePath As String, intermediatePath As String, metaDataValue As String, ByRef newItems As List(Of ITaskItem)) As String - Dim newDirectory = Path.Combine(intermediatePath, Path.GetDirectoryName(templatePath)) - Directory.CreateDirectory(newDirectory) - - ' Rewrite the .vstemplate file and save it - Dim newItemFilePath = Path.Combine(newDirectory, Path.GetFileName(templatePath)) - - ' Work around the inability of the SDK to take an absolute path here - Dim newItem = New TaskItem(newItemFilePath) - newItem.SetMetadata("OutputSubPath", metaDataValue) - newItems.Add(newItem) - - xml.Save(newItemFilePath) - Return newDirectory - End Function - - Private Function GetRelativePath(fromPath As String, toPath As String) As String - Dim fromAttr = GetPathAttribute(fromPath) - Dim toAttr = GetPathAttribute(toPath) - Dim path = New StringBuilder(260) - - If Not PathRelativePathTo(path, fromPath, fromAttr, toPath, toAttr) Then - Throw New ArgumentException($"Paths {fromPath} and {toPath} do not have a common prefix") - End If - - Return path.ToString() - End Function - - Private Declare Auto Function PathRelativePathTo Lib "shlwapi.dll" (pszPath As StringBuilder, pszFrom As String, dwAttrFrom As Integer, pszTo As String, dwAttrTo As Integer) As Boolean - - Public Sub CopyExtraFile(newDirectory As String, originalDirectory As String, extraFile As String) - Dim relativePath = extraFile.Substring(originalDirectory.Length) - - ' This intentionally isn't using path.combine since relative path may lead with a \ - Dim fullPath = Path.GetFullPath(newDirectory + relativePath) - - Try - Directory.CreateDirectory(Path.GetDirectoryName(fullPath)) - File.Copy(extraFile, fullPath) - - ' Ensure the copy isn't read only - Dim copyFileInfo As New FileInfo(fullPath) - copyFileInfo.IsReadOnly = False - Catch ex As Exception - End Try - End Sub - - Private Function GetPathAttribute(path As String) As Integer - Dim directoryInfo = New DirectoryInfo(path) - If directoryInfo.Exists Then - ' This magic hexadecimal number represents a folder. - Return &H10 - End If - - Dim fileInfo = New FileInfo(path) - If fileInfo.Exists Then - ' This magic hexadecimal number represents a file. - Return &H80 - End If - - ' If the path doesn't exist assume it to be a folder. - Return &H10 - End Function - - Private Shared Function IsReferencedTemplate(template As ITaskItem, extraFile As FileInfo) As Boolean - Return IsReferencedCSharpTemplate(template, extraFile) OrElse - IsReferencedVisualBasicTemplate(template, extraFile) - End Function - - Private Shared Function IsReferencedCSharpTemplate(template As ITaskItem, extraFile As FileInfo) As Boolean - Return (template.ItemSpec.Contains("CSharpDiagnostic.vstemplate") Or - template.ItemSpec.Contains("CSRef.vstemplate")) AndAlso - extraFile.FullName.Contains("CSharp") AndAlso - (extraFile.FullName.Contains("DiagnosticAnalyzer") Or - extraFile.FullName.Contains("Test") Or - extraFile.FullName.Contains("Vsix") Or - extraFile.FullName.Contains("CodeRefactoring")) - End Function - - Private Shared Function IsReferencedVisualBasicTemplate(template As ITaskItem, extraFile As FileInfo) As Boolean - Return (template.ItemSpec.Contains("VBDiagnostic.vstemplate") Or - template.ItemSpec.Contains("VBRef.vstemplate")) AndAlso - extraFile.FullName.Contains("VisualBasic") AndAlso - (extraFile.FullName.Contains("DiagnosticAnalyzer") Or - extraFile.FullName.Contains("Test") Or - extraFile.FullName.Contains("Vsix") Or - extraFile.FullName.Contains("CodeRefactoring")) - End Function -End Class diff --git a/build/Tasks/UpdateTemplateVersion/UpdateTemplateVersion.vbproj b/build/Tasks/UpdateTemplateVersion/UpdateTemplateVersion.vbproj deleted file mode 100644 index 5df574b5d..000000000 --- a/build/Tasks/UpdateTemplateVersion/UpdateTemplateVersion.vbproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - net461 - - - - - - - diff --git a/build/Toolset.proj b/build/Toolset.proj deleted file mode 100644 index af98856be..000000000 --- a/build/Toolset.proj +++ /dev/null @@ -1,9 +0,0 @@ - - - - net462 - - - - - \ No newline at end of file diff --git a/build/Versions.props b/build/Versions.props deleted file mode 100644 index 05a927ba2..000000000 --- a/build/Versions.props +++ /dev/null @@ -1,59 +0,0 @@ - - - 2.5.0 - beta1 - - - true - - - 1.0.0-alpha47 - 1.0.47 - - - 15.3.409 - 15.3.409 - 15.3.409 - - - 1.1.0 - 1.0.1 - 1.0.1 - 1.0.1 - 1.0.1 - 1.0.1 - 1.0.1 - 1.0.1 - 2.8.5 - 1.1.36 - 1.0.31 - 1.0.21 - - - 7.0.3300 - 12.0.4 - 8.0.1 - 8.0.0 - 15.0.26606 - 15.0.26606 - 15.0.26606 - 7.10.6070 - 14.3.25407 - 7.10.6071 - 8.0.50727 - 9.0.30729 - 10.0.30319 - 15.0.26606 - 15.0.26606 - 7.10.6070 - 15.0.26606 - 15.0.26606 - 15.3.20 - - - 1.3.1 - 1.1.0 - 1.4.2 - - - \ No newline at end of file diff --git a/build/build.proj b/build/build.proj deleted file mode 100644 index 331348f58..000000000 --- a/build/build.proj +++ /dev/null @@ -1,40 +0,0 @@ - - - - $(MSBuildThisFileDirectory)..\Roslyn-SDK.sln - - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/build.ps1 b/build/build.ps1 deleted file mode 100644 index 5e37f8951..000000000 --- a/build/build.ps1 +++ /dev/null @@ -1,158 +0,0 @@ -[CmdletBinding(PositionalBinding=$false)] -Param( - [string] $configuration = "Debug", - [string] $solution = "", - [string] $verbosity = "minimal", - [switch] $restore, - [switch] $deployDeps, - [switch] $build, - [switch] $rebuild, - [switch] $deploy, - [switch] $test, - [switch] $integrationTest, - [switch] $sign, - [switch] $pack, - [switch] $ci, - [switch] $prepareMachine, - [switch] $log, - [switch] $help, - [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties -) - -set-strictmode -version 2.0 -$ErrorActionPreference = "Stop" - -function Print-Usage() { - Write-Host "Common settings:" - Write-Host " -configuration Build configuration Debug, Release" - Write-Host " -verbosity Msbuild verbosity (q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic])" - Write-Host " -help Print help and exit" - Write-Host "" - - Write-Host "Actions:" - Write-Host " -restore Restore dependencies" - Write-Host " -build Build solution" - Write-Host " -rebuild Rebuild solution" - Write-Host " -deploy Deploy built VSIXes" - Write-Host " -deployDeps Deploy dependencies (Roslyn VSIXes for integration tests)" - Write-Host " -test Run all unit tests in the solution" - Write-Host " -integrationTest Run all integration tests in the solution" - Write-Host " -sign Sign build outputs" - Write-Host "" - - Write-Host "Advanced settings:" - Write-Host " -solution Path to solution to build" - Write-Host " -ci Set when running on CI server" - Write-Host " -log Enable logging (by default on CI)" - Write-Host " -prepareMachine Prepare machine for CI run" - Write-Host "" - Write-Host "Command line arguments not listed above are passed thru to msbuild." - Write-Host "The above arguments can be shortened as much as to be unambiguous (e.g. -co for configuration, -t for test, etc.)." -} - -if ($help -or (($properties -ne $null) -and ($properties.Contains("/help") -or $properties.Contains("/?")))) { - Print-Usage - exit 0 -} - -$RepoRoot = Join-Path $PSScriptRoot "..\" -$ToolsRoot = Join-Path $RepoRoot ".tools" -$BuildProj = Join-Path $PSScriptRoot "build.proj" -$DependenciesProps = Join-Path $PSScriptRoot "Versions.props" -$ArtifactsDir = Join-Path $RepoRoot "artifacts" -$LogDir = Join-Path (Join-Path $ArtifactsDir $configuration) "log" -$TempDir = Join-Path (Join-Path $ArtifactsDir $configuration) "tmp" - -function Create-Directory([string[]] $path) { - if (!(Test-Path -path $path)) { - New-Item -path $path -force -itemType "Directory" | Out-Null - } -} - -function GetVSWhereVersion { - [xml]$xml = Get-Content $DependenciesProps - return $xml.Project.PropertyGroup.VSWhereVersion -} - -function LocateVisualStudio { - $vswhereVersion = GetVSWhereVersion - $vsWhereDir = Join-Path $ToolsRoot "vswhere\$vswhereVersion" - $vsWhereExe = Join-Path $vsWhereDir "vswhere.exe" - - if (!(Test-Path $vsWhereExe)) { - Create-Directory $vsWhereDir - Invoke-WebRequest "http://github.com/Microsoft/vswhere/releases/download/$vswhereVersion/vswhere.exe" -OutFile $vswhereExe - } - - $vsInstallDir = & $vsWhereExe -latest -property installationPath -requires Microsoft.Component.MSBuild -requires Microsoft.VisualStudio.Component.VSSDK -requires Microsoft.Net.Component.4.6.TargetingPack -requires Microsoft.VisualStudio.Component.Roslyn.Compiler -requires Microsoft.VisualStudio.Component.VSSDK - - if (!(Test-Path $vsInstallDir)) { - throw "Failed to locate Visual Studio (exit code '$lastExitCode')." - } - - return $vsInstallDir -} - -function Build { - $vsInstallDir = LocateVisualStudio - $msbuildExe = Join-Path $vsInstallDir "MSBuild\15.0\Bin\msbuild.exe" - - # Microbuild is on 15.1 which doesn't support binary log - if ($ci -or $log) { - Create-Directory($logDir) - - if ($env:BUILD_BUILDNUMBER -eq $null) { - $logCmd = "/bl:" + (Join-Path $LogDir "Build.binlog") - } else { - $logCmd = "/flp1:Summary;Verbosity=diagnostic;Encoding=UTF-8;LogFile=" + (Join-Path $LogDir "Build.log") - } - } else { - $logCmd = "" - } - - $nodeReuse = "false" - - & $msbuildExe $BuildProj /m /nologo /clp:Summary /nodeReuse:$nodeReuse /warnaserror /v:$verbosity $logCmd /p:Configuration=$configuration /p:SolutionPath=$solution /p:Restore=$restore /p:DeployDeps=$deployDeps /p:Build=$build /p:Rebuild=$rebuild /p:Deploy=$deploy /p:Test=$test /p:IntegrationTest=$integrationTest /p:Sign=$sign /p:Pack=$pack /p:CIBuild=$ci $properties -} - -function Stop-Processes() { - Write-Host "Killing running build processes..." - Get-Process -Name "msbuild" -ErrorAction SilentlyContinue | Stop-Process - Get-Process -Name "vbcscompiler" -ErrorAction SilentlyContinue | Stop-Process -} - -function Clear-NuGetCache() { - # clean nuget packages -- necessary to avoid mismatching versions of swix microbuild build plugin and VSSDK on Jenkins - $nugetRoot = (Join-Path $env:USERPROFILE ".nuget\packages") - if (Test-Path $nugetRoot) { - Remove-Item $nugetRoot -Recurse -Force - } -} - -try { - if ($ci) { - Create-Directory $TempDir - $env:TEMP = $TempDir - $env:TMP = $TempDir - } - - # Preparation of a CI machine - if ($prepareMachine) { - Clear-NuGetCache - } - - Build - exit $lastExitCode -} -catch { - Write-Host $_ - Write-Host $_.Exception - Write-Host $_.ScriptStackTrace - exit 1 -} -finally { - Pop-Location - if ($ci -and $prepareMachine) { - Stop-Processes - } -} \ No newline at end of file diff --git a/build/publish.ps1 b/build/publish.ps1 deleted file mode 100644 index 32dd0a68f..000000000 --- a/build/publish.ps1 +++ /dev/null @@ -1,57 +0,0 @@ -# Publishes our build assets to nuget, myget, dotnet/versions, etc .. -# -# The publish operation is best visioned as an optional yet repeatable post build operation. It can be -# run anytime after build or automatically as a post build step. But it is an operation that focuses on -# build outputs and hence can't rely on source code from the build being available -# -# Repeatable is important here because we have to assume that publishes can and will fail with some -# degree of regularity. -[CmdletBinding(PositionalBinding = $false)] -Param( - # Standard options - [string]$vsixPath = "", - [string]$uploadUrl = "", - [switch]$test, - - # Credentials - [string]$apiKey = "" -) - -Set-StrictMode -version 2.0 -$ErrorActionPreference = "Stop" - -# Publish the VSIX packages to the specified URL -function Publish-Vsix() { - Write-Host "Publishing VSIX to $uploadUrl" - if (-not (Test-Path $vsixPath)) { - throw "VSIX $vsixPath does not exist" - } - - Write-Host " Publishing '$vsixPath'" - if (-not $test) { - $response = Invoke-WebRequest -Uri $uploadUrl -Headers @{"X-NuGet-ApiKey" = $apiKey} -ContentType 'multipart/form-data' -InFile $vsixPath -Method Post -UseBasicParsing - if ($response.StatusCode -ne 201) { - throw "Failed to upload VSIX extension: $vsixPath. Upload failed with Status code: $response.StatusCode" - } - } -} - -try { - if ($vsixPath -eq "") { - Write-Host "Must provide the path to the VSIX with -vsixPath" - exit 1 - } - - if ($uploadUrl -eq "") { - Write-Host "Must provide the URL to upload the VSIX to -uploadUrl" - exit 1 - } - - Publish-Vsix -} -catch { - Write-Host $_ - Write-Host $_.Exception - Write-Host $_.ScriptStackTrace - exit 1 -} \ No newline at end of file diff --git a/eng/Build.props b/eng/Build.props new file mode 100644 index 000000000..e37622dbe --- /dev/null +++ b/eng/Build.props @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/eng/PRBuild.cmd b/eng/PRBuild.cmd new file mode 100644 index 000000000..d660c27b8 --- /dev/null +++ b/eng/PRBuild.cmd @@ -0,0 +1,3 @@ +@echo off +powershell -ExecutionPolicy ByPass -command "& """%~dp0common\Build.ps1""" -restore -build -ci %*" +exit /b %ErrorLevel% \ No newline at end of file diff --git a/eng/Publishing.props b/eng/Publishing.props new file mode 100644 index 000000000..797de4ea1 --- /dev/null +++ b/eng/Publishing.props @@ -0,0 +1,6 @@ + + + + 3 + + \ No newline at end of file diff --git a/eng/Signing.props b/eng/Signing.props new file mode 100644 index 000000000..ea1aa0b58 --- /dev/null +++ b/eng/Signing.props @@ -0,0 +1,6 @@ + + + + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml new file mode 100644 index 000000000..90c1df549 --- /dev/null +++ b/eng/Version.Details.xml @@ -0,0 +1,11 @@ + + + + + + + https://github.com/dotnet/arcade + 85a65ea1fca1d0867f699fed44d191358270bf6a + + + diff --git a/eng/Versions.props b/eng/Versions.props new file mode 100644 index 000000000..ccf080935 --- /dev/null +++ b/eng/Versions.props @@ -0,0 +1,81 @@ + + + + + 4.0.0 + 1.1.1 + beta1 + + true + true + true + true + true + 3.9.0 + + 16.1.1 + + + + + 4.0.0-1.21267.34 + 17.0.0-previews-1-31318-291 + 17.0.65-g6c25c21ba5 + + 3.3.2 + $(MicrosoftCodeAnalysisPackagesVersion) + $(MicrosoftCodeAnalysisPackagesVersion) + $(MicrosoftCodeAnalysisPackagesVersion) + $(MicrosoftCodeAnalysisPackagesVersion) + $(MicrosoftCodeAnalysisPackagesVersion) + + 3.9.0 + + $(MicrosoftVisualStudioShellPackagesVersion) + $(MicrosoftVisualStudioShellPackagesVersion) + 2.8.2018 + 16.9.6-alpha + $(MicrosoftVisualStudioEditorPackagesVersion) + $(MicrosoftVisualStudioEditorPackagesVersion) + $(MicrosoftVisualStudioEditorPackagesVersion) + $(MicrosoftVisualStudioShellPackagesVersion) + $(MicrosoftVisualStudioShellPackagesVersion) + $(MicrosoftVisualStudioShellPackagesVersion) + $(MicrosoftVisualStudioShellPackagesVersion) + $(MicrosoftVisualStudioEditorPackagesVersion) + 16.9.2-alpha + $(MicrosoftVisualStudioShellPackagesVersion) + $(MicrosoftVisualStudioShellPackagesVersion) + $(MicrosoftVisualStudioShellPackagesVersion) + $(MicrosoftVisualStudioEditorPackagesVersion) + $(MicrosoftVisualStudioEditorPackagesVersion) + $(MicrosoftVisualStudioShellPackagesVersion) + $(MicrosoftVisualStudioEditorPackagesVersion) + 17.0.15-alpha + 16.3.23 + 17.0.11-alpha + 5.6.0 + 2.7.67 + $(MicrosoftVisualStudioShellPackagesVersion) + 17.0.0-preview-1-30928-1111 + 17.0.1056-Dev17PIAs-g9dffd635 + 1.1.33 + 5.0.0 + + 17.0.0-beta1-10413-02 + 17.0.0-beta1-10413-02 + 17.0.667-pre + + 1.5.0 + + 2.6.1 + 3.9.0 + 1.0.1-beta1.20374.2 + $(xunitVersion) + 1.2.7 + 0.1.49-beta + + 2.9.8 + 1.2.0-beta.164 + + diff --git a/eng/common/CIBuild.cmd b/eng/common/CIBuild.cmd new file mode 100644 index 000000000..56c2f25ac --- /dev/null +++ b/eng/common/CIBuild.cmd @@ -0,0 +1,2 @@ +@echo off +powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0Build.ps1""" -restore -build -test -sign -pack -publish -ci %*" \ No newline at end of file diff --git a/eng/common/PSScriptAnalyzerSettings.psd1 b/eng/common/PSScriptAnalyzerSettings.psd1 new file mode 100644 index 000000000..4c1ea7c98 --- /dev/null +++ b/eng/common/PSScriptAnalyzerSettings.psd1 @@ -0,0 +1,11 @@ +@{ + IncludeRules=@('PSAvoidUsingCmdletAliases', + 'PSAvoidUsingWMICmdlet', + 'PSAvoidUsingPositionalParameters', + 'PSAvoidUsingInvokeExpression', + 'PSUseDeclaredVarsMoreThanAssignments', + 'PSUseCmdletCorrectly', + 'PSStandardDSCFunctionsInResource', + 'PSUseIdenticalMandatoryParametersForDSC', + 'PSUseIdenticalParametersForDSC') +} \ No newline at end of file diff --git a/eng/common/README.md b/eng/common/README.md new file mode 100644 index 000000000..ff49c3715 --- /dev/null +++ b/eng/common/README.md @@ -0,0 +1,28 @@ +# Don't touch this folder + + uuuuuuuuuuuuuuuuuuuu + u" uuuuuuuuuuuuuuuuuu "u + u" u$$$$$$$$$$$$$$$$$$$$u "u + u" u$$$$$$$$$$$$$$$$$$$$$$$$u "u + u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u + u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u + u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u + $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ + $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ + $ $$$" ... "$... ...$" ... "$$$ ... "$$$ $ + $ $$$u `"$$$$$$$ $$$ $$$$$ $$ $$$ $$$ $ + $ $$$$$$uu "$$$$ $$$ $$$$$ $$ """ u$$$ $ + $ $$$""$$$ $$$$ $$$u "$$$" u$$ $$$$$$$$ $ + $ $$$$....,$$$$$..$$$$$....,$$$$..$$$$$$$$ $ + $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ + "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u" + "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u" + "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u" + "u "$$$$$$$$$$$$$$$$$$$$$$$$" u" + "u "$$$$$$$$$$$$$$$$$$$$" u" + "u """""""""""""""""" u" + """""""""""""""""""" + +!!! Changes made in this directory are subject to being overwritten by automation !!! + +The files in this directory are shared by all Arcade repos and managed by automation. If you need to make changes to these files, open an issue or submit a pull request to https://github.com/dotnet/arcade first. diff --git a/eng/common/SetupNugetSources.ps1 b/eng/common/SetupNugetSources.ps1 new file mode 100644 index 000000000..a0b5fc37f --- /dev/null +++ b/eng/common/SetupNugetSources.ps1 @@ -0,0 +1,161 @@ +# This file is a temporary workaround for internal builds to be able to restore from private AzDO feeds. +# This file should be removed as part of this issue: https://github.com/dotnet/arcade/issues/4080 +# +# What the script does is iterate over all package sources in the pointed NuGet.config and add a credential entry +# under for each Maestro managed private feed. Two additional credential +# entries are also added for the two private static internal feeds: dotnet3-internal and dotnet3-internal-transport. +# +# This script needs to be called in every job that will restore packages and which the base repo has +# private AzDO feeds in the NuGet.config. +# +# See example YAML call for this script below. Note the use of the variable `$(dn-bot-dnceng-artifact-feeds-rw)` +# from the AzureDevOps-Artifact-Feeds-Pats variable group. +# +# Any disabledPackageSources entries which start with "darc-int" will be re-enabled as part of this script executing +# +# - task: PowerShell@2 +# displayName: Setup Private Feeds Credentials +# condition: eq(variables['Agent.OS'], 'Windows_NT') +# inputs: +# filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 +# arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token +# env: +# Token: $(dn-bot-dnceng-artifact-feeds-rw) + +[CmdletBinding()] +param ( + [Parameter(Mandatory = $true)][string]$ConfigFile, + [Parameter(Mandatory = $true)][string]$Password +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version 2.0 +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +. $PSScriptRoot\tools.ps1 + +# Add source entry to PackageSources +function AddPackageSource($sources, $SourceName, $SourceEndPoint, $creds, $Username, $Password) { + $packageSource = $sources.SelectSingleNode("add[@key='$SourceName']") + + if ($packageSource -eq $null) + { + $packageSource = $doc.CreateElement("add") + $packageSource.SetAttribute("key", $SourceName) + $packageSource.SetAttribute("value", $SourceEndPoint) + $sources.AppendChild($packageSource) | Out-Null + } + else { + Write-Host "Package source $SourceName already present." + } + + AddCredential -Creds $creds -Source $SourceName -Username $Username -Password $Password +} + +# Add a credential node for the specified source +function AddCredential($creds, $source, $username, $password) { + # Looks for credential configuration for the given SourceName. Create it if none is found. + $sourceElement = $creds.SelectSingleNode($Source) + if ($sourceElement -eq $null) + { + $sourceElement = $doc.CreateElement($Source) + $creds.AppendChild($sourceElement) | Out-Null + } + + # Add the node to the credential if none is found. + $usernameElement = $sourceElement.SelectSingleNode("add[@key='Username']") + if ($usernameElement -eq $null) + { + $usernameElement = $doc.CreateElement("add") + $usernameElement.SetAttribute("key", "Username") + $sourceElement.AppendChild($usernameElement) | Out-Null + } + $usernameElement.SetAttribute("value", $Username) + + # Add the to the credential if none is found. + # Add it as a clear text because there is no support for encrypted ones in non-windows .Net SDKs. + # -> https://github.com/NuGet/Home/issues/5526 + $passwordElement = $sourceElement.SelectSingleNode("add[@key='ClearTextPassword']") + if ($passwordElement -eq $null) + { + $passwordElement = $doc.CreateElement("add") + $passwordElement.SetAttribute("key", "ClearTextPassword") + $sourceElement.AppendChild($passwordElement) | Out-Null + } + $passwordElement.SetAttribute("value", $Password) +} + +function InsertMaestroPrivateFeedCredentials($Sources, $Creds, $Username, $Password) { + $maestroPrivateSources = $Sources.SelectNodes("add[contains(@key,'darc-int')]") + + Write-Host "Inserting credentials for $($maestroPrivateSources.Count) Maestro's private feeds." + + ForEach ($PackageSource in $maestroPrivateSources) { + Write-Host "`tInserting credential for Maestro's feed:" $PackageSource.Key + AddCredential -Creds $creds -Source $PackageSource.Key -Username $Username -Password $Password + } +} + +function EnablePrivatePackageSources($DisabledPackageSources) { + $maestroPrivateSources = $DisabledPackageSources.SelectNodes("add[contains(@key,'darc-int')]") + ForEach ($DisabledPackageSource in $maestroPrivateSources) { + Write-Host "`tEnsuring private source '$($DisabledPackageSource.key)' is enabled by deleting it from disabledPackageSource" + # Due to https://github.com/NuGet/Home/issues/10291, we must actually remove the disabled entries + $DisabledPackageSources.RemoveChild($DisabledPackageSource) + } +} + +if (!(Test-Path $ConfigFile -PathType Leaf)) { + Write-PipelineTelemetryError -Category 'Build' -Message "Eng/common/SetupNugetSources.ps1 returned a non-zero exit code. Couldn't find the NuGet config file: $ConfigFile" + ExitWithExitCode 1 +} + +if (!$Password) { + Write-PipelineTelemetryError -Category 'Build' -Message 'Eng/common/SetupNugetSources.ps1 returned a non-zero exit code. Please supply a valid PAT' + ExitWithExitCode 1 +} + +# Load NuGet.config +$doc = New-Object System.Xml.XmlDocument +$filename = (Get-Item $ConfigFile).FullName +$doc.Load($filename) + +# Get reference to or create one if none exist already +$sources = $doc.DocumentElement.SelectSingleNode("packageSources") +if ($sources -eq $null) { + $sources = $doc.CreateElement("packageSources") + $doc.DocumentElement.AppendChild($sources) | Out-Null +} + +# Looks for a node. Create it if none is found. +$creds = $doc.DocumentElement.SelectSingleNode("packageSourceCredentials") +if ($creds -eq $null) { + $creds = $doc.CreateElement("packageSourceCredentials") + $doc.DocumentElement.AppendChild($creds) | Out-Null +} + +# Check for disabledPackageSources; we'll enable any darc-int ones we find there +$disabledSources = $doc.DocumentElement.SelectSingleNode("disabledPackageSources") +if ($disabledSources -ne $null) { + Write-Host "Checking for any darc-int disabled package sources in the disabledPackageSources node" + EnablePrivatePackageSources -DisabledPackageSources $disabledSources +} + +$userName = "dn-bot" + +# Insert credential nodes for Maestro's private feeds +InsertMaestroPrivateFeedCredentials -Sources $sources -Creds $creds -Username $userName -Password $Password + +$dotnet31Source = $sources.SelectSingleNode("add[@key='dotnet3.1']") +if ($dotnet31Source -ne $null) { + AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v2" -Creds $creds -Username $userName -Password $Password + AddPackageSource -Sources $sources -SourceName "dotnet3.1-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v2" -Creds $creds -Username $userName -Password $Password +} + +$dotnet5Source = $sources.SelectSingleNode("add[@key='dotnet5']") +if ($dotnet5Source -ne $null) { + AddPackageSource -Sources $sources -SourceName "dotnet5-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet5-internal/nuget/v2" -Creds $creds -Username $userName -Password $Password + AddPackageSource -Sources $sources -SourceName "dotnet5-internal-transport" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet5-internal-transport/nuget/v2" -Creds $creds -Username $userName -Password $Password +} + +$doc.Save($filename) diff --git a/eng/common/SetupNugetSources.sh b/eng/common/SetupNugetSources.sh new file mode 100644 index 000000000..2734601c1 --- /dev/null +++ b/eng/common/SetupNugetSources.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +# This file is a temporary workaround for internal builds to be able to restore from private AzDO feeds. +# This file should be removed as part of this issue: https://github.com/dotnet/arcade/issues/4080 +# +# What the script does is iterate over all package sources in the pointed NuGet.config and add a credential entry +# under for each Maestro's managed private feed. Two additional credential +# entries are also added for the two private static internal feeds: dotnet3-internal and dotnet3-internal-transport. +# +# This script needs to be called in every job that will restore packages and which the base repo has +# private AzDO feeds in the NuGet.config. +# +# See example YAML call for this script below. Note the use of the variable `$(dn-bot-dnceng-artifact-feeds-rw)` +# from the AzureDevOps-Artifact-Feeds-Pats variable group. +# +# Any disabledPackageSources entries which start with "darc-int" will be re-enabled as part of this script executing. +# +# - task: Bash@3 +# displayName: Setup Private Feeds Credentials +# inputs: +# filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh +# arguments: $(Build.SourcesDirectory)/NuGet.config $Token +# condition: ne(variables['Agent.OS'], 'Windows_NT') +# env: +# Token: $(dn-bot-dnceng-artifact-feeds-rw) + +ConfigFile=$1 +CredToken=$2 +NL='\n' +TB=' ' + +source="${BASH_SOURCE[0]}" + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. "$scriptroot/tools.sh" + +if [ ! -f "$ConfigFile" ]; then + Write-PipelineTelemetryError -Category 'Build' "Error: Eng/common/SetupNugetSources.sh returned a non-zero exit code. Couldn't find the NuGet config file: $ConfigFile" + ExitWithExitCode 1 +fi + +if [ -z "$CredToken" ]; then + Write-PipelineTelemetryError -category 'Build' "Error: Eng/common/SetupNugetSources.sh returned a non-zero exit code. Please supply a valid PAT" + ExitWithExitCode 1 +fi + +if [[ `uname -s` == "Darwin" ]]; then + NL=$'\\\n' + TB='' +fi + +# Ensure there is a ... section. +grep -i "" $ConfigFile +if [ "$?" != "0" ]; then + echo "Adding ... section." + ConfigNodeHeader="" + PackageSourcesTemplate="${TB}${NL}${TB}" + + sed -i.bak "s|$ConfigNodeHeader|$ConfigNodeHeader${NL}$PackageSourcesTemplate|" $ConfigFile +fi + +# Ensure there is a ... section. +grep -i "" $ConfigFile +if [ "$?" != "0" ]; then + echo "Adding ... section." + + PackageSourcesNodeFooter="" + PackageSourceCredentialsTemplate="${TB}${NL}${TB}" + + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourcesNodeFooter${NL}$PackageSourceCredentialsTemplate|" $ConfigFile +fi + +PackageSources=() + +# Ensure dotnet3.1-internal and dotnet3.1-internal-transport are in the packageSources if the public dotnet3.1 feeds are present +grep -i "" + + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile + fi + PackageSources+=('dotnet3.1-internal') + + grep -i "" $ConfigFile + if [ "$?" != "0" ]; then + echo "Adding dotnet3.1-internal-transport to the packageSources." + PackageSourcesNodeFooter="" + PackageSourceTemplate="${TB}" + + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile + fi + PackageSources+=('dotnet3.1-internal-transport') +fi + +# Ensure dotnet5-internal and dotnet5-internal-transport are in the packageSources if the public dotnet5 feeds are present +grep -i "" + + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile + fi + PackageSources+=('dotnet5-internal') + + grep -i "" $ConfigFile + if [ "$?" != "0" ]; then + echo "Adding dotnet5-internal-transport to the packageSources." + PackageSourcesNodeFooter="" + PackageSourceTemplate="${TB}" + + sed -i.bak "s|$PackageSourcesNodeFooter|$PackageSourceTemplate${NL}$PackageSourcesNodeFooter|" $ConfigFile + fi + PackageSources+=('dotnet5-internal-transport') +fi + +# I want things split line by line +PrevIFS=$IFS +IFS=$'\n' +PackageSources+="$IFS" +PackageSources+=$(grep -oh '"darc-int-[^"]*"' $ConfigFile | tr -d '"') +IFS=$PrevIFS + +for FeedName in ${PackageSources[@]} ; do + # Check if there is no existing credential for this FeedName + grep -i "<$FeedName>" $ConfigFile + if [ "$?" != "0" ]; then + echo "Adding credentials for $FeedName." + + PackageSourceCredentialsNodeFooter="" + NewCredential="${TB}${TB}<$FeedName>${NL}${NL}${NL}" + + sed -i.bak "s|$PackageSourceCredentialsNodeFooter|$NewCredential${NL}$PackageSourceCredentialsNodeFooter|" $ConfigFile + fi +done + +# Re-enable any entries in disabledPackageSources where the feed name contains darc-int +grep -i "" $ConfigFile +if [ "$?" == "0" ]; then + DisabledDarcIntSources=() + echo "Re-enabling any disabled \"darc-int\" package sources in $ConfigFile" + DisabledDarcIntSources+=$(grep -oh '"darc-int-[^"]*" value="true"' $ConfigFile | tr -d '"') + for DisabledSourceName in ${DisabledDarcIntSources[@]} ; do + if [[ $DisabledSourceName == darc-int* ]] + then + OldDisableValue="" + NewDisableValue="" + sed -i.bak "s|$OldDisableValue|$NewDisableValue|" $ConfigFile + echo "Neutralized disablePackageSources entry for '$DisabledSourceName'" + fi + done +fi diff --git a/eng/common/build.ps1 b/eng/common/build.ps1 new file mode 100644 index 000000000..8943da242 --- /dev/null +++ b/eng/common/build.ps1 @@ -0,0 +1,161 @@ +[CmdletBinding(PositionalBinding=$false)] +Param( + [string][Alias('c')]$configuration = "Debug", + [string]$platform = $null, + [string] $projects, + [string][Alias('v')]$verbosity = "minimal", + [string] $msbuildEngine = $null, + [bool] $warnAsError = $true, + [bool] $nodeReuse = $true, + [switch][Alias('r')]$restore, + [switch] $deployDeps, + [switch][Alias('b')]$build, + [switch] $rebuild, + [switch] $deploy, + [switch][Alias('t')]$test, + [switch] $integrationTest, + [switch] $performanceTest, + [switch] $sign, + [switch] $pack, + [switch] $publish, + [switch] $clean, + [switch][Alias('bl')]$binaryLog, + [switch][Alias('nobl')]$excludeCIBinarylog, + [switch] $ci, + [switch] $prepareMachine, + [string] $runtimeSourceFeed = '', + [string] $runtimeSourceFeedKey = '', + [switch] $excludePrereleaseVS, + [switch] $help, + [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties +) + +# Unset 'Platform' environment variable to avoid unwanted collision in InstallDotNetCore.targets file +# some computer has this env var defined (e.g. Some HP) +if($env:Platform) { + $env:Platform="" +} +function Print-Usage() { + Write-Host "Common settings:" + Write-Host " -configuration Build configuration: 'Debug' or 'Release' (short: -c)" + Write-Host " -platform Platform configuration: 'x86', 'x64' or any valid Platform value to pass to msbuild" + Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)" + Write-Host " -binaryLog Output binary log (short: -bl)" + Write-Host " -help Print help and exit" + Write-Host "" + + Write-Host "Actions:" + Write-Host " -restore Restore dependencies (short: -r)" + Write-Host " -build Build solution (short: -b)" + Write-Host " -rebuild Rebuild solution" + Write-Host " -deploy Deploy built VSIXes" + Write-Host " -deployDeps Deploy dependencies (e.g. VSIXes for integration tests)" + Write-Host " -test Run all unit tests in the solution (short: -t)" + Write-Host " -integrationTest Run all integration tests in the solution" + Write-Host " -performanceTest Run all performance tests in the solution" + Write-Host " -pack Package build outputs into NuGet packages and Willow components" + Write-Host " -sign Sign build outputs" + Write-Host " -publish Publish artifacts (e.g. symbols)" + Write-Host " -clean Clean the solution" + Write-Host "" + + Write-Host "Advanced settings:" + Write-Host " -projects Semi-colon delimited list of sln/proj's to build. Globbing is supported (*.sln)" + Write-Host " -ci Set when running on CI server" + Write-Host " -excludeCIBinarylog Don't output binary log (short: -nobl)" + Write-Host " -prepareMachine Prepare machine for CI run, clean up processes after build" + Write-Host " -warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" + Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." + Write-Host " -excludePrereleaseVS Set to exclude build engines in prerelease versions of Visual Studio" + Write-Host "" + + Write-Host "Command line arguments not listed above are passed thru to msbuild." + Write-Host "The above arguments can be shortened as much as to be unambiguous (e.g. -co for configuration, -t for test, etc.)." +} + +. $PSScriptRoot\tools.ps1 + +function InitializeCustomToolset { + if (-not $restore) { + return + } + + $script = Join-Path $EngRoot 'restore-toolset.ps1' + + if (Test-Path $script) { + . $script + } +} + +function Build { + $toolsetBuildProj = InitializeToolset + InitializeCustomToolset + + $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'Build.binlog') } else { '' } + $platformArg = if ($platform) { "/p:Platform=$platform" } else { '' } + + if ($projects) { + # Re-assign properties to a new variable because PowerShell doesn't let us append properties directly for unclear reasons. + # Explicitly set the type as string[] because otherwise PowerShell would make this char[] if $properties is empty. + [string[]] $msbuildArgs = $properties + + # Resolve relative project paths into full paths + $projects = ($projects.Split(';').ForEach({Resolve-Path $_}) -join ';') + + $msbuildArgs += "/p:Projects=$projects" + $properties = $msbuildArgs + } + + MSBuild $toolsetBuildProj ` + $bl ` + $platformArg ` + /p:Configuration=$configuration ` + /p:RepoRoot=$RepoRoot ` + /p:Restore=$restore ` + /p:DeployDeps=$deployDeps ` + /p:Build=$build ` + /p:Rebuild=$rebuild ` + /p:Deploy=$deploy ` + /p:Test=$test ` + /p:Pack=$pack ` + /p:IntegrationTest=$integrationTest ` + /p:PerformanceTest=$performanceTest ` + /p:Sign=$sign ` + /p:Publish=$publish ` + @properties +} + +try { + if ($clean) { + if (Test-Path $ArtifactsDir) { + Remove-Item -Recurse -Force $ArtifactsDir + Write-Host 'Artifacts directory deleted.' + } + exit 0 + } + + if ($help -or (($null -ne $properties) -and ($properties.Contains('/help') -or $properties.Contains('/?')))) { + Print-Usage + exit 0 + } + + if ($ci) { + if (-not $excludeCIBinarylog) { + $binaryLog = $true + } + $nodeReuse = $false + } + + if ($restore) { + InitializeNativeTools + } + + Build +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ + ExitWithExitCode 1 +} + +ExitWithExitCode 0 diff --git a/eng/common/build.sh b/eng/common/build.sh new file mode 100755 index 000000000..55b298f16 --- /dev/null +++ b/eng/common/build.sh @@ -0,0 +1,232 @@ +#!/usr/bin/env bash + +# Stop script if unbound variable found (use ${var:-} if intentional) +set -u + +# Stop script if command returns non-zero exit code. +# Prevents hidden errors caused by missing error code propagation. +set -e + +usage() +{ + echo "Common settings:" + echo " --configuration Build configuration: 'Debug' or 'Release' (short: -c)" + echo " --verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)" + echo " --binaryLog Create MSBuild binary log (short: -bl)" + echo " --help Print help and exit (short: -h)" + echo "" + + echo "Actions:" + echo " --restore Restore dependencies (short: -r)" + echo " --build Build solution (short: -b)" + echo " --rebuild Rebuild solution" + echo " --test Run all unit tests in the solution (short: -t)" + echo " --integrationTest Run all integration tests in the solution" + echo " --performanceTest Run all performance tests in the solution" + echo " --pack Package build outputs into NuGet packages and Willow components" + echo " --sign Sign build outputs" + echo " --publish Publish artifacts (e.g. symbols)" + echo " --clean Clean the solution" + echo "" + + echo "Advanced settings:" + echo " --projects Project or solution file(s) to build" + echo " --ci Set when running on CI server" + echo " --excludeCIBinarylog Don't output binary log (short: -nobl)" + echo " --prepareMachine Prepare machine for CI run, clean up processes after build" + echo " --nodeReuse Sets nodereuse msbuild parameter ('true' or 'false')" + echo " --warnAsError Sets warnaserror msbuild parameter ('true' or 'false')" + echo "" + echo "Command line arguments not listed above are passed thru to msbuild." + echo "Arguments can also be passed in with a single hyphen." +} + +source="${BASH_SOURCE[0]}" + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +restore=false +build=false +rebuild=false +test=false +integration_test=false +performance_test=false +pack=false +publish=false +sign=false +public=false +ci=false +clean=false + +warn_as_error=true +node_reuse=true +binary_log=false +exclude_ci_binary_log=false +pipelines_log=false + +projects='' +configuration='Debug' +prepare_machine=false +verbosity='minimal' +runtime_source_feed='' +runtime_source_feed_key='' + +properties='' +while [[ $# > 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -help|-h) + usage + exit 0 + ;; + -clean) + clean=true + ;; + -configuration|-c) + configuration=$2 + shift + ;; + -verbosity|-v) + verbosity=$2 + shift + ;; + -binarylog|-bl) + binary_log=true + ;; + -excludeCIBinarylog|-nobl) + exclude_ci_binary_log=true + ;; + -pipelineslog|-pl) + pipelines_log=true + ;; + -restore|-r) + restore=true + ;; + -build|-b) + build=true + ;; + -rebuild) + rebuild=true + ;; + -pack) + pack=true + ;; + -test|-t) + test=true + ;; + -integrationtest) + integration_test=true + ;; + -performancetest) + performance_test=true + ;; + -sign) + sign=true + ;; + -publish) + publish=true + ;; + -preparemachine) + prepare_machine=true + ;; + -projects) + projects=$2 + shift + ;; + -ci) + ci=true + ;; + -warnaserror) + warn_as_error=$2 + shift + ;; + -nodereuse) + node_reuse=$2 + shift + ;; + -runtimesourcefeed) + runtime_source_feed=$2 + shift + ;; + -runtimesourcefeedkey) + runtime_source_feed_key=$2 + shift + ;; + *) + properties="$properties $1" + ;; + esac + + shift +done + +if [[ "$ci" == true ]]; then + pipelines_log=true + node_reuse=false + if [[ "$exclude_ci_binary_log" == false ]]; then + binary_log=true + fi +fi + +. "$scriptroot/tools.sh" + +function InitializeCustomToolset { + local script="$eng_root/restore-toolset.sh" + + if [[ -a "$script" ]]; then + . "$script" + fi +} + +function Build { + InitializeToolset + InitializeCustomToolset + + if [[ ! -z "$projects" ]]; then + properties="$properties /p:Projects=$projects" + fi + + local bl="" + if [[ "$binary_log" == true ]]; then + bl="/bl:\"$log_dir/Build.binlog\"" + fi + + MSBuild $_InitializeToolset \ + $bl \ + /p:Configuration=$configuration \ + /p:RepoRoot="$repo_root" \ + /p:Restore=$restore \ + /p:Build=$build \ + /p:Rebuild=$rebuild \ + /p:Test=$test \ + /p:Pack=$pack \ + /p:IntegrationTest=$integration_test \ + /p:PerformanceTest=$performance_test \ + /p:Sign=$sign \ + /p:Publish=$publish \ + $properties + + ExitWithExitCode 0 +} + +if [[ "$clean" == true ]]; then + if [ -d "$artifacts_dir" ]; then + rm -rf $artifacts_dir + echo "Artifacts directory deleted." + fi + exit 0 +fi + +if [[ "$restore" == true ]]; then + InitializeNativeTools +fi + +Build diff --git a/eng/common/cibuild.sh b/eng/common/cibuild.sh new file mode 100755 index 000000000..1a02c0dec --- /dev/null +++ b/eng/common/cibuild.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +# resolve $SOURCE until the file is no longer a symlink +while [[ -h $source ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + + # if $source was a relative symlink, we need to resolve it relative to the path where + # the symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. "$scriptroot/build.sh" --restore --build --test --pack --publish --ci $@ \ No newline at end of file diff --git a/eng/common/cross/arm/sources.list.bionic b/eng/common/cross/arm/sources.list.bionic new file mode 100644 index 000000000..210955740 --- /dev/null +++ b/eng/common/cross/arm/sources.list.bionic @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse diff --git a/eng/common/cross/arm/sources.list.jessie b/eng/common/cross/arm/sources.list.jessie new file mode 100644 index 000000000..4d142ac9b --- /dev/null +++ b/eng/common/cross/arm/sources.list.jessie @@ -0,0 +1,3 @@ +# Debian (sid) # UNSTABLE +deb http://ftp.debian.org/debian/ sid main contrib non-free +deb-src http://ftp.debian.org/debian/ sid main contrib non-free diff --git a/eng/common/cross/arm/sources.list.trusty b/eng/common/cross/arm/sources.list.trusty new file mode 100644 index 000000000..07d8f88d8 --- /dev/null +++ b/eng/common/cross/arm/sources.list.trusty @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ trusty main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ trusty-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ trusty-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ trusty-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty-security main restricted universe multiverse \ No newline at end of file diff --git a/eng/common/cross/arm/sources.list.xenial b/eng/common/cross/arm/sources.list.xenial new file mode 100644 index 000000000..eacd86b7d --- /dev/null +++ b/eng/common/cross/arm/sources.list.xenial @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted universe multiverse \ No newline at end of file diff --git a/eng/common/cross/arm/sources.list.zesty b/eng/common/cross/arm/sources.list.zesty new file mode 100644 index 000000000..ea2c14a78 --- /dev/null +++ b/eng/common/cross/arm/sources.list.zesty @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ zesty main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-security main restricted universe multiverse diff --git a/eng/common/cross/arm/trusty-lttng-2.4.patch b/eng/common/cross/arm/trusty-lttng-2.4.patch new file mode 100644 index 000000000..8e4dd7ae7 --- /dev/null +++ b/eng/common/cross/arm/trusty-lttng-2.4.patch @@ -0,0 +1,71 @@ +From e72c9d7ead60e3317bd6d1fade995c07021c947b Mon Sep 17 00:00:00 2001 +From: Mathieu Desnoyers +Date: Thu, 7 May 2015 13:25:04 -0400 +Subject: [PATCH] Fix: building probe providers with C++ compiler + +Robert Daniels wrote: +> > I'm attempting to use lttng userspace tracing with a C++ application +> > on an ARM platform. I'm using GCC 4.8.4 on Linux 3.14 with the 2.6 +> > release of lttng. I've compiled lttng-modules, lttng-ust, and +> > lttng-tools and have been able to get a simple test working with C +> > code. When I attempt to run the hello.cxx test on my target it will +> > segfault. +> +> +> I spent a little time digging into this issue and finally discovered the +> cause of my segfault with ARM C++ tracepoints. +> +> There is a struct called 'lttng_event' in ust-events.h which contains an +> empty union 'u'. This was the cause of my issue. Under C, this empty union +> compiles to a zero byte member while under C++ it compiles to a one byte +> member, and in my case was four-byte aligned which caused my C++ code to +> have the 'cds_list_head node' offset incorrectly by four bytes. This lead +> to an incorrect linked list structure which caused my issue. +> +> Since this union is empty, I simply removed it from the struct and everything +> worked correctly. +> +> I don't know the history or purpose behind this empty union so I'd like to +> know if this is a safe fix. If it is I can submit a patch with the union +> removed. + +That's a very nice catch! + +We do not support building tracepoint probe provider with +g++ yet, as stated in lttng-ust(3): + +"- Note for C++ support: although an application instrumented with + tracepoints can be compiled with g++, tracepoint probes should be + compiled with gcc (only tested with gcc so far)." + +However, if it works fine with this fix, then I'm tempted to take it, +especially because removing the empty union does not appear to affect +the layout of struct lttng_event as seen from liblttng-ust, which must +be compiled with a C compiler, and from probe providers compiled with +a C compiler. So all we are changing is the layout of a probe provider +compiled with a C++ compiler, which is anyway buggy at the moment, +because it is not compatible with the layout expected by liblttng-ust +compiled with a C compiler. + +Reported-by: Robert Daniels +Signed-off-by: Mathieu Desnoyers +--- + include/lttng/ust-events.h | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/usr/include/lttng/ust-events.h b/usr/include/lttng/ust-events.h +index 328a875..3d7a274 100644 +--- a/usr/include/lttng/ust-events.h ++++ b/usr/include/lttng/ust-events.h +@@ -407,8 +407,6 @@ struct lttng_event { + void *_deprecated1; + struct lttng_ctx *ctx; + enum lttng_ust_instrumentation instrumentation; +- union { +- } u; + struct cds_list_head node; /* Event list in session */ + struct cds_list_head _deprecated2; + void *_deprecated3; +-- +2.7.4 + diff --git a/eng/common/cross/arm/trusty.patch b/eng/common/cross/arm/trusty.patch new file mode 100644 index 000000000..2f2972f8e --- /dev/null +++ b/eng/common/cross/arm/trusty.patch @@ -0,0 +1,97 @@ +diff -u -r a/usr/include/urcu/uatomic/generic.h b/usr/include/urcu/uatomic/generic.h +--- a/usr/include/urcu/uatomic/generic.h 2014-03-28 06:04:42.000000000 +0900 ++++ b/usr/include/urcu/uatomic/generic.h 2017-02-13 10:35:21.189927116 +0900 +@@ -65,17 +65,17 @@ + switch (len) { + #ifdef UATOMIC_HAS_ATOMIC_BYTE + case 1: +- return __sync_val_compare_and_swap_1(addr, old, _new); ++ return __sync_val_compare_and_swap_1((uint8_t *) addr, old, _new); + #endif + #ifdef UATOMIC_HAS_ATOMIC_SHORT + case 2: +- return __sync_val_compare_and_swap_2(addr, old, _new); ++ return __sync_val_compare_and_swap_2((uint16_t *) addr, old, _new); + #endif + case 4: +- return __sync_val_compare_and_swap_4(addr, old, _new); ++ return __sync_val_compare_and_swap_4((uint32_t *) addr, old, _new); + #if (CAA_BITS_PER_LONG == 64) + case 8: +- return __sync_val_compare_and_swap_8(addr, old, _new); ++ return __sync_val_compare_and_swap_8((uint64_t *) addr, old, _new); + #endif + } + _uatomic_link_error(); +@@ -100,20 +100,20 @@ + switch (len) { + #ifdef UATOMIC_HAS_ATOMIC_BYTE + case 1: +- __sync_and_and_fetch_1(addr, val); ++ __sync_and_and_fetch_1((uint8_t *) addr, val); + return; + #endif + #ifdef UATOMIC_HAS_ATOMIC_SHORT + case 2: +- __sync_and_and_fetch_2(addr, val); ++ __sync_and_and_fetch_2((uint16_t *) addr, val); + return; + #endif + case 4: +- __sync_and_and_fetch_4(addr, val); ++ __sync_and_and_fetch_4((uint32_t *) addr, val); + return; + #if (CAA_BITS_PER_LONG == 64) + case 8: +- __sync_and_and_fetch_8(addr, val); ++ __sync_and_and_fetch_8((uint64_t *) addr, val); + return; + #endif + } +@@ -139,20 +139,20 @@ + switch (len) { + #ifdef UATOMIC_HAS_ATOMIC_BYTE + case 1: +- __sync_or_and_fetch_1(addr, val); ++ __sync_or_and_fetch_1((uint8_t *) addr, val); + return; + #endif + #ifdef UATOMIC_HAS_ATOMIC_SHORT + case 2: +- __sync_or_and_fetch_2(addr, val); ++ __sync_or_and_fetch_2((uint16_t *) addr, val); + return; + #endif + case 4: +- __sync_or_and_fetch_4(addr, val); ++ __sync_or_and_fetch_4((uint32_t *) addr, val); + return; + #if (CAA_BITS_PER_LONG == 64) + case 8: +- __sync_or_and_fetch_8(addr, val); ++ __sync_or_and_fetch_8((uint64_t *) addr, val); + return; + #endif + } +@@ -180,17 +180,17 @@ + switch (len) { + #ifdef UATOMIC_HAS_ATOMIC_BYTE + case 1: +- return __sync_add_and_fetch_1(addr, val); ++ return __sync_add_and_fetch_1((uint8_t *) addr, val); + #endif + #ifdef UATOMIC_HAS_ATOMIC_SHORT + case 2: +- return __sync_add_and_fetch_2(addr, val); ++ return __sync_add_and_fetch_2((uint16_t *) addr, val); + #endif + case 4: +- return __sync_add_and_fetch_4(addr, val); ++ return __sync_add_and_fetch_4((uint32_t *) addr, val); + #if (CAA_BITS_PER_LONG == 64) + case 8: +- return __sync_add_and_fetch_8(addr, val); ++ return __sync_add_and_fetch_8((uint64_t *) addr, val); + #endif + } + _uatomic_link_error(); diff --git a/eng/common/cross/arm64/sources.list.bionic b/eng/common/cross/arm64/sources.list.bionic new file mode 100644 index 000000000..210955740 --- /dev/null +++ b/eng/common/cross/arm64/sources.list.bionic @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse diff --git a/eng/common/cross/arm64/sources.list.buster b/eng/common/cross/arm64/sources.list.buster new file mode 100644 index 000000000..7194ac64a --- /dev/null +++ b/eng/common/cross/arm64/sources.list.buster @@ -0,0 +1,11 @@ +deb http://deb.debian.org/debian buster main +deb-src http://deb.debian.org/debian buster main + +deb http://deb.debian.org/debian-security/ buster/updates main +deb-src http://deb.debian.org/debian-security/ buster/updates main + +deb http://deb.debian.org/debian buster-updates main +deb-src http://deb.debian.org/debian buster-updates main + +deb http://deb.debian.org/debian buster-backports main contrib non-free +deb-src http://deb.debian.org/debian buster-backports main contrib non-free diff --git a/eng/common/cross/arm64/sources.list.stretch b/eng/common/cross/arm64/sources.list.stretch new file mode 100644 index 000000000..0e1215774 --- /dev/null +++ b/eng/common/cross/arm64/sources.list.stretch @@ -0,0 +1,12 @@ +deb http://deb.debian.org/debian stretch main +deb-src http://deb.debian.org/debian stretch main + +deb http://deb.debian.org/debian-security/ stretch/updates main +deb-src http://deb.debian.org/debian-security/ stretch/updates main + +deb http://deb.debian.org/debian stretch-updates main +deb-src http://deb.debian.org/debian stretch-updates main + +deb http://deb.debian.org/debian stretch-backports main contrib non-free +deb-src http://deb.debian.org/debian stretch-backports main contrib non-free + diff --git a/eng/common/cross/arm64/sources.list.trusty b/eng/common/cross/arm64/sources.list.trusty new file mode 100644 index 000000000..07d8f88d8 --- /dev/null +++ b/eng/common/cross/arm64/sources.list.trusty @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ trusty main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ trusty-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ trusty-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ trusty-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ trusty-security main restricted universe multiverse \ No newline at end of file diff --git a/eng/common/cross/arm64/sources.list.xenial b/eng/common/cross/arm64/sources.list.xenial new file mode 100644 index 000000000..eacd86b7d --- /dev/null +++ b/eng/common/cross/arm64/sources.list.xenial @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted universe multiverse \ No newline at end of file diff --git a/eng/common/cross/arm64/sources.list.zesty b/eng/common/cross/arm64/sources.list.zesty new file mode 100644 index 000000000..ea2c14a78 --- /dev/null +++ b/eng/common/cross/arm64/sources.list.zesty @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ zesty main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ zesty-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ zesty-security main restricted universe multiverse diff --git a/eng/common/cross/arm64/tizen-build-rootfs.sh b/eng/common/cross/arm64/tizen-build-rootfs.sh new file mode 100644 index 000000000..13bfddb5e --- /dev/null +++ b/eng/common/cross/arm64/tizen-build-rootfs.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -e + +__CrossDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +__TIZEN_CROSSDIR="$__CrossDir/tizen" + +if [[ -z "$ROOTFS_DIR" ]]; then + echo "ROOTFS_DIR is not defined." + exit 1; +fi + +TIZEN_TMP_DIR=$ROOTFS_DIR/tizen_tmp +mkdir -p $TIZEN_TMP_DIR + +# Download files +echo ">>Start downloading files" +VERBOSE=1 $__CrossDir/tizen-fetch.sh $TIZEN_TMP_DIR +echo "<>Start constructing Tizen rootfs" +TIZEN_RPM_FILES=`ls $TIZEN_TMP_DIR/*.rpm` +cd $ROOTFS_DIR +for f in $TIZEN_RPM_FILES; do + rpm2cpio $f | cpio -idm --quiet +done +echo "<>Start configuring Tizen rootfs" +ln -sfn asm-arm64 ./usr/include/asm +patch -p1 < $__TIZEN_CROSSDIR/tizen.patch +echo "</dev/null; then + VERBOSE=0 +fi + +Log() +{ + if [ $VERBOSE -ge $1 ]; then + echo ${@:2} + fi +} + +Inform() +{ + Log 1 -e "\x1B[0;34m$@\x1B[m" +} + +Debug() +{ + Log 2 -e "\x1B[0;32m$@\x1B[m" +} + +Error() +{ + >&2 Log 0 -e "\x1B[0;31m$@\x1B[m" +} + +Fetch() +{ + URL=$1 + FILE=$2 + PROGRESS=$3 + if [ $VERBOSE -ge 1 ] && [ $PROGRESS ]; then + CURL_OPT="--progress-bar" + else + CURL_OPT="--silent" + fi + curl $CURL_OPT $URL > $FILE +} + +hash curl 2> /dev/null || { Error "Require 'curl' Aborting."; exit 1; } +hash xmllint 2> /dev/null || { Error "Require 'xmllint' Aborting."; exit 1; } +hash sha256sum 2> /dev/null || { Error "Require 'sha256sum' Aborting."; exit 1; } + +TMPDIR=$1 +if [ ! -d $TMPDIR ]; then + TMPDIR=./tizen_tmp + Debug "Create temporary directory : $TMPDIR" + mkdir -p $TMPDIR +fi + +TIZEN_URL=http://download.tizen.org/snapshots/tizen/ +BUILD_XML=build.xml +REPOMD_XML=repomd.xml +PRIMARY_XML=primary.xml +TARGET_URL="http://__not_initialized" + +Xpath_get() +{ + XPATH_RESULT='' + XPATH=$1 + XML_FILE=$2 + RESULT=$(xmllint --xpath $XPATH $XML_FILE) + if [[ -z ${RESULT// } ]]; then + Error "Can not find target from $XML_FILE" + Debug "Xpath = $XPATH" + exit 1 + fi + XPATH_RESULT=$RESULT +} + +fetch_tizen_pkgs_init() +{ + TARGET=$1 + PROFILE=$2 + Debug "Initialize TARGET=$TARGET, PROFILE=$PROFILE" + + TMP_PKG_DIR=$TMPDIR/tizen_${PROFILE}_pkgs + if [ -d $TMP_PKG_DIR ]; then rm -rf $TMP_PKG_DIR; fi + mkdir -p $TMP_PKG_DIR + + PKG_URL=$TIZEN_URL/$PROFILE/latest + + BUILD_XML_URL=$PKG_URL/$BUILD_XML + TMP_BUILD=$TMP_PKG_DIR/$BUILD_XML + TMP_REPOMD=$TMP_PKG_DIR/$REPOMD_XML + TMP_PRIMARY=$TMP_PKG_DIR/$PRIMARY_XML + TMP_PRIMARYGZ=${TMP_PRIMARY}.gz + + Fetch $BUILD_XML_URL $TMP_BUILD + + Debug "fetch $BUILD_XML_URL to $TMP_BUILD" + + TARGET_XPATH="//build/buildtargets/buildtarget[@name=\"$TARGET\"]/repo[@type=\"binary\"]/text()" + Xpath_get $TARGET_XPATH $TMP_BUILD + TARGET_PATH=$XPATH_RESULT + TARGET_URL=$PKG_URL/$TARGET_PATH + + REPOMD_URL=$TARGET_URL/repodata/repomd.xml + PRIMARY_XPATH='string(//*[local-name()="data"][@type="primary"]/*[local-name()="location"]/@href)' + + Fetch $REPOMD_URL $TMP_REPOMD + + Debug "fetch $REPOMD_URL to $TMP_REPOMD" + + Xpath_get $PRIMARY_XPATH $TMP_REPOMD + PRIMARY_XML_PATH=$XPATH_RESULT + PRIMARY_URL=$TARGET_URL/$PRIMARY_XML_PATH + + Fetch $PRIMARY_URL $TMP_PRIMARYGZ + + Debug "fetch $PRIMARY_URL to $TMP_PRIMARYGZ" + + gunzip $TMP_PRIMARYGZ + + Debug "unzip $TMP_PRIMARYGZ to $TMP_PRIMARY" +} + +fetch_tizen_pkgs() +{ + ARCH=$1 + PACKAGE_XPATH_TPL='string(//*[local-name()="metadata"]/*[local-name()="package"][*[local-name()="name"][text()="_PKG_"]][*[local-name()="arch"][text()="_ARCH_"]]/*[local-name()="location"]/@href)' + + PACKAGE_CHECKSUM_XPATH_TPL='string(//*[local-name()="metadata"]/*[local-name()="package"][*[local-name()="name"][text()="_PKG_"]][*[local-name()="arch"][text()="_ARCH_"]]/*[local-name()="checksum"]/text())' + + for pkg in ${@:2} + do + Inform "Fetching... $pkg" + XPATH=${PACKAGE_XPATH_TPL/_PKG_/$pkg} + XPATH=${XPATH/_ARCH_/$ARCH} + Xpath_get $XPATH $TMP_PRIMARY + PKG_PATH=$XPATH_RESULT + + XPATH=${PACKAGE_CHECKSUM_XPATH_TPL/_PKG_/$pkg} + XPATH=${XPATH/_ARCH_/$ARCH} + Xpath_get $XPATH $TMP_PRIMARY + CHECKSUM=$XPATH_RESULT + + PKG_URL=$TARGET_URL/$PKG_PATH + PKG_FILE=$(basename $PKG_PATH) + PKG_PATH=$TMPDIR/$PKG_FILE + + Debug "Download $PKG_URL to $PKG_PATH" + Fetch $PKG_URL $PKG_PATH true + + echo "$CHECKSUM $PKG_PATH" | sha256sum -c - > /dev/null + if [ $? -ne 0 ]; then + Error "Fail to fetch $PKG_URL to $PKG_PATH" + Debug "Checksum = $CHECKSUM" + exit 1 + fi + done +} + +Inform "Initialize arm base" +fetch_tizen_pkgs_init standard base +Inform "fetch common packages" +fetch_tizen_pkgs aarch64 gcc glibc glibc-devel libicu libicu-devel libatomic linux-glibc-devel keyutils keyutils-devel libkeyutils +Inform "fetch coreclr packages" +fetch_tizen_pkgs aarch64 lldb lldb-devel libgcc libstdc++ libstdc++-devel libunwind libunwind-devel lttng-ust-devel lttng-ust userspace-rcu-devel userspace-rcu +Inform "fetch corefx packages" +fetch_tizen_pkgs aarch64 libcom_err libcom_err-devel zlib zlib-devel libopenssl11 libopenssl1.1-devel krb5 krb5-devel + +Inform "Initialize standard unified" +fetch_tizen_pkgs_init standard unified +Inform "fetch corefx packages" +fetch_tizen_pkgs aarch64 gssdp gssdp-devel tizen-release + diff --git a/eng/common/cross/arm64/tizen/tizen.patch b/eng/common/cross/arm64/tizen/tizen.patch new file mode 100644 index 000000000..af7c8be05 --- /dev/null +++ b/eng/common/cross/arm64/tizen/tizen.patch @@ -0,0 +1,9 @@ +diff -u -r a/usr/lib/libc.so b/usr/lib/libc.so +--- a/usr/lib64/libc.so 2016-12-30 23:00:08.284951863 +0900 ++++ b/usr/lib64/libc.so 2016-12-30 23:00:32.140951815 +0900 +@@ -2,4 +2,4 @@ + Use the shared library, but some functions are only in + the static library, so try that secondarily. */ + OUTPUT_FORMAT(elf64-littleaarch64) +-GROUP ( /lib64/libc.so.6 /usr/lib64/libc_nonshared.a AS_NEEDED ( /lib/ld-linux-aarch64.so.1 ) ) ++GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux-aarch64.so.1 ) ) diff --git a/eng/common/cross/armel/armel.jessie.patch b/eng/common/cross/armel/armel.jessie.patch new file mode 100644 index 000000000..2d2615619 --- /dev/null +++ b/eng/common/cross/armel/armel.jessie.patch @@ -0,0 +1,43 @@ +diff -u -r a/usr/include/urcu/uatomic/generic.h b/usr/include/urcu/uatomic/generic.h +--- a/usr/include/urcu/uatomic/generic.h 2014-10-22 15:00:58.000000000 -0700 ++++ b/usr/include/urcu/uatomic/generic.h 2020-10-30 21:38:28.550000000 -0700 +@@ -69,10 +69,10 @@ + #endif + #ifdef UATOMIC_HAS_ATOMIC_SHORT + case 2: +- return __sync_val_compare_and_swap_2(addr, old, _new); ++ return __sync_val_compare_and_swap_2((uint16_t*) addr, old, _new); + #endif + case 4: +- return __sync_val_compare_and_swap_4(addr, old, _new); ++ return __sync_val_compare_and_swap_4((uint32_t*) addr, old, _new); + #if (CAA_BITS_PER_LONG == 64) + case 8: + return __sync_val_compare_and_swap_8(addr, old, _new); +@@ -109,7 +109,7 @@ + return; + #endif + case 4: +- __sync_and_and_fetch_4(addr, val); ++ __sync_and_and_fetch_4((uint32_t*) addr, val); + return; + #if (CAA_BITS_PER_LONG == 64) + case 8: +@@ -148,7 +148,7 @@ + return; + #endif + case 4: +- __sync_or_and_fetch_4(addr, val); ++ __sync_or_and_fetch_4((uint32_t*) addr, val); + return; + #if (CAA_BITS_PER_LONG == 64) + case 8: +@@ -187,7 +187,7 @@ + return __sync_add_and_fetch_2(addr, val); + #endif + case 4: +- return __sync_add_and_fetch_4(addr, val); ++ return __sync_add_and_fetch_4((uint32_t*) addr, val); + #if (CAA_BITS_PER_LONG == 64) + case 8: + return __sync_add_and_fetch_8(addr, val); diff --git a/eng/common/cross/armel/sources.list.jessie b/eng/common/cross/armel/sources.list.jessie new file mode 100644 index 000000000..3d9c3059d --- /dev/null +++ b/eng/common/cross/armel/sources.list.jessie @@ -0,0 +1,3 @@ +# Debian (jessie) # Stable +deb http://ftp.debian.org/debian/ jessie main contrib non-free +deb-src http://ftp.debian.org/debian/ jessie main contrib non-free diff --git a/eng/common/cross/armel/tizen-build-rootfs.sh b/eng/common/cross/armel/tizen-build-rootfs.sh new file mode 100755 index 000000000..9a4438af6 --- /dev/null +++ b/eng/common/cross/armel/tizen-build-rootfs.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -e + +__ARM_SOFTFP_CrossDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +__TIZEN_CROSSDIR="$__ARM_SOFTFP_CrossDir/tizen" + +if [[ -z "$ROOTFS_DIR" ]]; then + echo "ROOTFS_DIR is not defined." + exit 1; +fi + +TIZEN_TMP_DIR=$ROOTFS_DIR/tizen_tmp +mkdir -p $TIZEN_TMP_DIR + +# Download files +echo ">>Start downloading files" +VERBOSE=1 $__ARM_SOFTFP_CrossDir/tizen-fetch.sh $TIZEN_TMP_DIR +echo "<>Start constructing Tizen rootfs" +TIZEN_RPM_FILES=`ls $TIZEN_TMP_DIR/*.rpm` +cd $ROOTFS_DIR +for f in $TIZEN_RPM_FILES; do + rpm2cpio $f | cpio -idm --quiet +done +echo "<>Start configuring Tizen rootfs" +ln -sfn asm-arm ./usr/include/asm +patch -p1 < $__TIZEN_CROSSDIR/tizen.patch +echo "</dev/null; then + VERBOSE=0 +fi + +Log() +{ + if [ $VERBOSE -ge $1 ]; then + echo ${@:2} + fi +} + +Inform() +{ + Log 1 -e "\x1B[0;34m$@\x1B[m" +} + +Debug() +{ + Log 2 -e "\x1B[0;32m$@\x1B[m" +} + +Error() +{ + >&2 Log 0 -e "\x1B[0;31m$@\x1B[m" +} + +Fetch() +{ + URL=$1 + FILE=$2 + PROGRESS=$3 + if [ $VERBOSE -ge 1 ] && [ $PROGRESS ]; then + CURL_OPT="--progress-bar" + else + CURL_OPT="--silent" + fi + curl $CURL_OPT $URL > $FILE +} + +hash curl 2> /dev/null || { Error "Require 'curl' Aborting."; exit 1; } +hash xmllint 2> /dev/null || { Error "Require 'xmllint' Aborting."; exit 1; } +hash sha256sum 2> /dev/null || { Error "Require 'sha256sum' Aborting."; exit 1; } + +TMPDIR=$1 +if [ ! -d $TMPDIR ]; then + TMPDIR=./tizen_tmp + Debug "Create temporary directory : $TMPDIR" + mkdir -p $TMPDIR +fi + +TIZEN_URL=http://download.tizen.org/snapshots/tizen +BUILD_XML=build.xml +REPOMD_XML=repomd.xml +PRIMARY_XML=primary.xml +TARGET_URL="http://__not_initialized" + +Xpath_get() +{ + XPATH_RESULT='' + XPATH=$1 + XML_FILE=$2 + RESULT=$(xmllint --xpath $XPATH $XML_FILE) + if [[ -z ${RESULT// } ]]; then + Error "Can not find target from $XML_FILE" + Debug "Xpath = $XPATH" + exit 1 + fi + XPATH_RESULT=$RESULT +} + +fetch_tizen_pkgs_init() +{ + TARGET=$1 + PROFILE=$2 + Debug "Initialize TARGET=$TARGET, PROFILE=$PROFILE" + + TMP_PKG_DIR=$TMPDIR/tizen_${PROFILE}_pkgs + if [ -d $TMP_PKG_DIR ]; then rm -rf $TMP_PKG_DIR; fi + mkdir -p $TMP_PKG_DIR + + PKG_URL=$TIZEN_URL/$PROFILE/latest + + BUILD_XML_URL=$PKG_URL/$BUILD_XML + TMP_BUILD=$TMP_PKG_DIR/$BUILD_XML + TMP_REPOMD=$TMP_PKG_DIR/$REPOMD_XML + TMP_PRIMARY=$TMP_PKG_DIR/$PRIMARY_XML + TMP_PRIMARYGZ=${TMP_PRIMARY}.gz + + Fetch $BUILD_XML_URL $TMP_BUILD + + Debug "fetch $BUILD_XML_URL to $TMP_BUILD" + + TARGET_XPATH="//build/buildtargets/buildtarget[@name=\"$TARGET\"]/repo[@type=\"binary\"]/text()" + Xpath_get $TARGET_XPATH $TMP_BUILD + TARGET_PATH=$XPATH_RESULT + TARGET_URL=$PKG_URL/$TARGET_PATH + + REPOMD_URL=$TARGET_URL/repodata/repomd.xml + PRIMARY_XPATH='string(//*[local-name()="data"][@type="primary"]/*[local-name()="location"]/@href)' + + Fetch $REPOMD_URL $TMP_REPOMD + + Debug "fetch $REPOMD_URL to $TMP_REPOMD" + + Xpath_get $PRIMARY_XPATH $TMP_REPOMD + PRIMARY_XML_PATH=$XPATH_RESULT + PRIMARY_URL=$TARGET_URL/$PRIMARY_XML_PATH + + Fetch $PRIMARY_URL $TMP_PRIMARYGZ + + Debug "fetch $PRIMARY_URL to $TMP_PRIMARYGZ" + + gunzip $TMP_PRIMARYGZ + + Debug "unzip $TMP_PRIMARYGZ to $TMP_PRIMARY" +} + +fetch_tizen_pkgs() +{ + ARCH=$1 + PACKAGE_XPATH_TPL='string(//*[local-name()="metadata"]/*[local-name()="package"][*[local-name()="name"][text()="_PKG_"]][*[local-name()="arch"][text()="_ARCH_"]]/*[local-name()="location"]/@href)' + + PACKAGE_CHECKSUM_XPATH_TPL='string(//*[local-name()="metadata"]/*[local-name()="package"][*[local-name()="name"][text()="_PKG_"]][*[local-name()="arch"][text()="_ARCH_"]]/*[local-name()="checksum"]/text())' + + for pkg in ${@:2} + do + Inform "Fetching... $pkg" + XPATH=${PACKAGE_XPATH_TPL/_PKG_/$pkg} + XPATH=${XPATH/_ARCH_/$ARCH} + Xpath_get $XPATH $TMP_PRIMARY + PKG_PATH=$XPATH_RESULT + + XPATH=${PACKAGE_CHECKSUM_XPATH_TPL/_PKG_/$pkg} + XPATH=${XPATH/_ARCH_/$ARCH} + Xpath_get $XPATH $TMP_PRIMARY + CHECKSUM=$XPATH_RESULT + + PKG_URL=$TARGET_URL/$PKG_PATH + PKG_FILE=$(basename $PKG_PATH) + PKG_PATH=$TMPDIR/$PKG_FILE + + Debug "Download $PKG_URL to $PKG_PATH" + Fetch $PKG_URL $PKG_PATH true + + echo "$CHECKSUM $PKG_PATH" | sha256sum -c - > /dev/null + if [ $? -ne 0 ]; then + Error "Fail to fetch $PKG_URL to $PKG_PATH" + Debug "Checksum = $CHECKSUM" + exit 1 + fi + done +} + +Inform "Initialize arm base" +fetch_tizen_pkgs_init standard base +Inform "fetch common packages" +fetch_tizen_pkgs armv7l gcc gcc-devel-static glibc glibc-devel libicu libicu-devel libatomic linux-glibc-devel keyutils keyutils-devel libkeyutils +Inform "fetch coreclr packages" +fetch_tizen_pkgs armv7l lldb lldb-devel libgcc libstdc++ libstdc++-devel libunwind libunwind-devel lttng-ust-devel lttng-ust userspace-rcu-devel userspace-rcu +Inform "fetch corefx packages" +fetch_tizen_pkgs armv7l libcom_err libcom_err-devel zlib zlib-devel libopenssl11 libopenssl1.1-devel krb5 krb5-devel + +Inform "Initialize standard unified" +fetch_tizen_pkgs_init standard unified +Inform "fetch corefx packages" +fetch_tizen_pkgs armv7l gssdp gssdp-devel tizen-release + diff --git a/eng/common/cross/armel/tizen/tizen-dotnet.ks b/eng/common/cross/armel/tizen/tizen-dotnet.ks new file mode 100644 index 000000000..506d455bd --- /dev/null +++ b/eng/common/cross/armel/tizen/tizen-dotnet.ks @@ -0,0 +1,50 @@ +lang en_US.UTF-8 +keyboard us +timezone --utc Asia/Seoul + +part / --fstype="ext4" --size=3500 --ondisk=mmcblk0 --label rootfs --fsoptions=defaults,noatime + +rootpw tizen +desktop --autologinuser=root +user --name root --groups audio,video --password 'tizen' + +repo --name=standard --baseurl=http://download.tizen.org/releases/milestone/tizen/unified/latest/repos/standard/packages/ --ssl_verify=no +repo --name=base --baseurl=http://download.tizen.org/releases/milestone/tizen/base/latest/repos/standard/packages/ --ssl_verify=no + +%packages +tar +gzip + +sed +grep +gawk +perl + +binutils +findutils +util-linux +lttng-ust +userspace-rcu +procps-ng +tzdata +ca-certificates + + +### Core FX +libicu +libunwind +iputils +zlib +krb5 +libcurl +libopenssl + +%end + +%post + +### Update /tmp privilege +chmod 777 /tmp +#################################### + +%end diff --git a/eng/common/cross/armel/tizen/tizen.patch b/eng/common/cross/armel/tizen/tizen.patch new file mode 100644 index 000000000..ca7c7c1ff --- /dev/null +++ b/eng/common/cross/armel/tizen/tizen.patch @@ -0,0 +1,9 @@ +diff -u -r a/usr/lib/libc.so b/usr/lib/libc.so +--- a/usr/lib/libc.so 2016-12-30 23:00:08.284951863 +0900 ++++ b/usr/lib/libc.so 2016-12-30 23:00:32.140951815 +0900 +@@ -2,4 +2,4 @@ + Use the shared library, but some functions are only in + the static library, so try that secondarily. */ + OUTPUT_FORMAT(elf32-littlearm) +-GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a AS_NEEDED ( /lib/ld-linux.so.3 ) ) ++GROUP ( libc.so.6 libc_nonshared.a AS_NEEDED ( ld-linux.so.3 ) ) diff --git a/eng/common/cross/build-android-rootfs.sh b/eng/common/cross/build-android-rootfs.sh new file mode 100755 index 000000000..42516bbee --- /dev/null +++ b/eng/common/cross/build-android-rootfs.sh @@ -0,0 +1,131 @@ +#!/usr/bin/env bash +set -e +__NDK_Version=r21 + +usage() +{ + echo "Creates a toolchain and sysroot used for cross-compiling for Android." + echo. + echo "Usage: $0 [BuildArch] [ApiLevel]" + echo. + echo "BuildArch is the target architecture of Android. Currently only arm64 is supported." + echo "ApiLevel is the target Android API level. API levels usually match to Android releases. See https://source.android.com/source/build-numbers.html" + echo. + echo "By default, the toolchain and sysroot will be generated in cross/android-rootfs/toolchain/[BuildArch]. You can change this behavior" + echo "by setting the TOOLCHAIN_DIR environment variable" + echo. + echo "By default, the NDK will be downloaded into the cross/android-rootfs/android-ndk-$__NDK_Version directory. If you already have an NDK installation," + echo "you can set the NDK_DIR environment variable to have this script use that installation of the NDK." + echo "By default, this script will generate a file, android_platform, in the root of the ROOTFS_DIR directory that contains the RID for the supported and tested Android build: android.28-arm64. This file is to replace '/etc/os-release', which is not available for Android." + exit 1 +} + +__ApiLevel=28 # The minimum platform for arm64 is API level 21 but the minimum version that support glob(3) is 28. See $ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/glob.h +__BuildArch=arm64 +__AndroidArch=aarch64 +__AndroidToolchain=aarch64-linux-android + +for i in "$@" + do + lowerI="$(echo $i | tr "[:upper:]" "[:lower:]")" + case $lowerI in + -?|-h|--help) + usage + exit 1 + ;; + arm64) + __BuildArch=arm64 + __AndroidArch=aarch64 + __AndroidToolchain=aarch64-linux-android + ;; + arm) + __BuildArch=arm + __AndroidArch=arm + __AndroidToolchain=arm-linux-androideabi + ;; + *[0-9]) + __ApiLevel=$i + ;; + *) + __UnprocessedBuildArgs="$__UnprocessedBuildArgs $i" + ;; + esac +done + +# Obtain the location of the bash script to figure out where the root of the repo is. +__ScriptBaseDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +__CrossDir="$__ScriptBaseDir/../../../.tools/android-rootfs" + +if [[ ! -f "$__CrossDir" ]]; then + mkdir -p "$__CrossDir" +fi + +# Resolve absolute path to avoid `../` in build logs +__CrossDir="$( cd "$__CrossDir" && pwd )" + +__NDK_Dir="$__CrossDir/android-ndk-$__NDK_Version" +__lldb_Dir="$__CrossDir/lldb" +__ToolchainDir="$__CrossDir/android-ndk-$__NDK_Version" + +if [[ -n "$TOOLCHAIN_DIR" ]]; then + __ToolchainDir=$TOOLCHAIN_DIR +fi + +if [[ -n "$NDK_DIR" ]]; then + __NDK_Dir=$NDK_DIR +fi + +echo "Target API level: $__ApiLevel" +echo "Target architecture: $__BuildArch" +echo "NDK location: $__NDK_Dir" +echo "Target Toolchain location: $__ToolchainDir" + +# Download the NDK if required +if [ ! -d $__NDK_Dir ]; then + echo Downloading the NDK into $__NDK_Dir + mkdir -p $__NDK_Dir + wget -q --progress=bar:force:noscroll --show-progress https://dl.google.com/android/repository/android-ndk-$__NDK_Version-linux-x86_64.zip -O $__CrossDir/android-ndk-$__NDK_Version-linux-x86_64.zip + unzip -q $__CrossDir/android-ndk-$__NDK_Version-linux-x86_64.zip -d $__CrossDir +fi + +if [ ! -d $__lldb_Dir ]; then + mkdir -p $__lldb_Dir + echo Downloading LLDB into $__lldb_Dir + wget -q --progress=bar:force:noscroll --show-progress https://dl.google.com/android/repository/lldb-2.3.3614996-linux-x86_64.zip -O $__CrossDir/lldb-2.3.3614996-linux-x86_64.zip + unzip -q $__CrossDir/lldb-2.3.3614996-linux-x86_64.zip -d $__lldb_Dir +fi + +echo "Download dependencies..." +__TmpDir=$__CrossDir/tmp/$__BuildArch/ +mkdir -p "$__TmpDir" + +# combined dependencies for coreclr, installer and libraries +__AndroidPackages="libicu" +__AndroidPackages+=" libandroid-glob" +__AndroidPackages+=" liblzma" +__AndroidPackages+=" krb5" +__AndroidPackages+=" openssl" + +for path in $(wget -qO- http://termux.net/dists/stable/main/binary-$__AndroidArch/Packages |\ + grep -A15 "Package: \(${__AndroidPackages// /\\|}\)" | grep -v "static\|tool" | grep Filename); do + + if [[ "$path" != "Filename:" ]]; then + echo "Working on: $path" + wget -qO- http://termux.net/$path | dpkg -x - "$__TmpDir" + fi +done + +cp -R "$__TmpDir/data/data/com.termux/files/usr/"* "$__ToolchainDir/sysroot/usr/" + +# Generate platform file for build.sh script to assign to __DistroRid +echo "Generating platform file..." +echo "RID=android.${__ApiLevel}-${__BuildArch}" > $__ToolchainDir/sysroot/android_platform + +echo "Now to build coreclr, libraries and installers; run:" +echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ + --subsetCategory coreclr +echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ + --subsetCategory libraries +echo ROOTFS_DIR=\$\(realpath $__ToolchainDir/sysroot\) ./build.sh --cross --arch $__BuildArch \ + --subsetCategory installer diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh new file mode 100755 index 000000000..591d8666a --- /dev/null +++ b/eng/common/cross/build-rootfs.sh @@ -0,0 +1,381 @@ +#!/usr/bin/env bash + +set -e + +usage() +{ + echo "Usage: $0 [BuildArch] [CodeName] [lldbx.y] [--skipunmount] --rootfsdir ]" + echo "BuildArch can be: arm(default), armel, arm64, x86" + echo "CodeName - optional, Code name for Linux, can be: trusty, xenial(default), zesty, bionic, alpine, alpine3.9 or alpine3.13. If BuildArch is armel, LinuxCodeName is jessie(default) or tizen." + echo " for FreeBSD can be: freebsd11 or freebsd12." + echo " for illumos can be: illumos." + echo "lldbx.y - optional, LLDB version, can be: lldb3.9(default), lldb4.0, lldb5.0, lldb6.0 no-lldb. Ignored for alpine and FReeBSD" + echo "--skipunmount - optional, will skip the unmount of rootfs folder." + echo "--use-mirror - optional, use mirror URL to fetch resources, when available." + exit 1 +} + +__CodeName=xenial +__CrossDir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +__InitialDir=$PWD +__BuildArch=arm +__AlpineArch=armv7 +__QEMUArch=arm +__UbuntuArch=armhf +__UbuntuRepo="http://ports.ubuntu.com/" +__LLDB_Package="liblldb-3.9-dev" +__SkipUnmount=0 + +# base development support +__UbuntuPackages="build-essential" + +__AlpinePackages="alpine-base" +__AlpinePackages+=" build-base" +__AlpinePackages+=" linux-headers" +__AlpinePackagesEdgeCommunity=" lldb-dev" +__AlpinePackagesEdgeMain=" llvm10-libs" +__AlpinePackagesEdgeMain+=" python3" +__AlpinePackagesEdgeMain+=" libedit" + +# symlinks fixer +__UbuntuPackages+=" symlinks" + +# CoreCLR and CoreFX dependencies +__UbuntuPackages+=" libicu-dev" +__UbuntuPackages+=" liblttng-ust-dev" +__UbuntuPackages+=" libunwind8-dev" + +__AlpinePackages+=" gettext-dev" +__AlpinePackages+=" icu-dev" +__AlpinePackages+=" libunwind-dev" +__AlpinePackages+=" lttng-ust-dev" + +# CoreFX dependencies +__UbuntuPackages+=" libcurl4-openssl-dev" +__UbuntuPackages+=" libkrb5-dev" +__UbuntuPackages+=" libssl-dev" +__UbuntuPackages+=" zlib1g-dev" + +__AlpinePackages+=" curl-dev" +__AlpinePackages+=" krb5-dev" +__AlpinePackages+=" openssl-dev" +__AlpinePackages+=" zlib-dev" + +__FreeBSDBase="12.1-RELEASE" +__FreeBSDPkg="1.12.0" +__FreeBSDPackages="libunwind" +__FreeBSDPackages+=" icu" +__FreeBSDPackages+=" libinotify" +__FreeBSDPackages+=" lttng-ust" +__FreeBSDPackages+=" krb5" + +__IllumosPackages="icu-64.2nb2" +__IllumosPackages+=" mit-krb5-1.16.2nb4" +__IllumosPackages+=" openssl-1.1.1e" +__IllumosPackages+=" zlib-1.2.11" + +# ML.NET dependencies +__UbuntuPackages+=" libomp5" +__UbuntuPackages+=" libomp-dev" + +__UseMirror=0 + +__UnprocessedBuildArgs= +while :; do + if [ $# -le 0 ]; then + break + fi + + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" + case $lowerI in + -?|-h|--help) + usage + exit 1 + ;; + arm) + __BuildArch=arm + __UbuntuArch=armhf + __AlpineArch=armv7 + __QEMUArch=arm + ;; + arm64) + __BuildArch=arm64 + __UbuntuArch=arm64 + __AlpineArch=aarch64 + __QEMUArch=aarch64 + ;; + armel) + __BuildArch=armel + __UbuntuArch=armel + __UbuntuRepo="http://ftp.debian.org/debian/" + __CodeName=jessie + ;; + s390x) + __BuildArch=s390x + __UbuntuArch=s390x + __UbuntuRepo="http://ports.ubuntu.com/ubuntu-ports/" + __UbuntuPackages=$(echo ${__UbuntuPackages} | sed 's/ libunwind8-dev//') + unset __LLDB_Package + ;; + x86) + __BuildArch=x86 + __UbuntuArch=i386 + __UbuntuRepo="http://archive.ubuntu.com/ubuntu/" + ;; + lldb3.6) + __LLDB_Package="lldb-3.6-dev" + ;; + lldb3.8) + __LLDB_Package="lldb-3.8-dev" + ;; + lldb3.9) + __LLDB_Package="liblldb-3.9-dev" + ;; + lldb4.0) + __LLDB_Package="liblldb-4.0-dev" + ;; + lldb5.0) + __LLDB_Package="liblldb-5.0-dev" + ;; + lldb6.0) + __LLDB_Package="liblldb-6.0-dev" + ;; + no-lldb) + unset __LLDB_Package + ;; + trusty) # Ubuntu 14.04 + if [ "$__CodeName" != "jessie" ]; then + __CodeName=trusty + fi + ;; + xenial) # Ubuntu 16.04 + if [ "$__CodeName" != "jessie" ]; then + __CodeName=xenial + fi + ;; + zesty) # Ubuntu 17.04 + if [ "$__CodeName" != "jessie" ]; then + __CodeName=zesty + fi + ;; + bionic) # Ubuntu 18.04 + if [ "$__CodeName" != "jessie" ]; then + __CodeName=bionic + fi + ;; + jessie) # Debian 8 + __CodeName=jessie + __UbuntuRepo="http://ftp.debian.org/debian/" + ;; + stretch) # Debian 9 + __CodeName=stretch + __UbuntuRepo="http://ftp.debian.org/debian/" + __LLDB_Package="liblldb-6.0-dev" + ;; + buster) # Debian 10 + __CodeName=buster + __UbuntuRepo="http://ftp.debian.org/debian/" + __LLDB_Package="liblldb-6.0-dev" + ;; + tizen) + if [ "$__BuildArch" != "armel" ] && [ "$__BuildArch" != "arm64" ]; then + echo "Tizen is available only for armel and arm64." + usage; + exit 1; + fi + __CodeName= + __UbuntuRepo= + __Tizen=tizen + ;; + alpine|alpine3.9) + __CodeName=alpine + __UbuntuRepo= + __AlpineVersion=3.9 + ;; + alpine3.13) + __CodeName=alpine + __UbuntuRepo= + __AlpineVersion=3.13 + # Alpine 3.13 has all the packages we need in the 3.13 repository + __AlpinePackages+=$__AlpinePackagesEdgeCommunity + __AlpinePackagesEdgeCommunity= + __AlpinePackages+=$__AlpinePackagesEdgeMain + __AlpinePackagesEdgeMain= + ;; + freebsd11) + __FreeBSDBase="11.3-RELEASE" + ;& + freebsd12) + __CodeName=freebsd + __BuildArch=x64 + __SkipUnmount=1 + ;; + illumos) + __CodeName=illumos + __BuildArch=x64 + __SkipUnmount=1 + ;; + --skipunmount) + __SkipUnmount=1 + ;; + --rootfsdir|-rootfsdir) + shift + __RootfsDir=$1 + ;; + --use-mirror) + __UseMirror=1 + ;; + *) + __UnprocessedBuildArgs="$__UnprocessedBuildArgs $1" + ;; + esac + + shift +done + +if [ "$__BuildArch" == "armel" ]; then + __LLDB_Package="lldb-3.5-dev" +fi +__UbuntuPackages+=" ${__LLDB_Package:-}" + +if [ -z "$__RootfsDir" ] && [ ! -z "$ROOTFS_DIR" ]; then + __RootfsDir=$ROOTFS_DIR +fi + +if [ -z "$__RootfsDir" ]; then + __RootfsDir="$__CrossDir/../../../.tools/rootfs/$__BuildArch" +fi + +if [ -d "$__RootfsDir" ]; then + if [ $__SkipUnmount == 0 ]; then + umount $__RootfsDir/* || true + fi + rm -rf $__RootfsDir +fi + +mkdir -p $__RootfsDir +__RootfsDir="$( cd "$__RootfsDir" && pwd )" + +if [[ "$__CodeName" == "alpine" ]]; then + __ApkToolsVersion=2.9.1 + __ApkToolsDir=$(mktemp -d) + wget https://github.com/alpinelinux/apk-tools/releases/download/v$__ApkToolsVersion/apk-tools-$__ApkToolsVersion-x86_64-linux.tar.gz -P $__ApkToolsDir + tar -xf $__ApkToolsDir/apk-tools-$__ApkToolsVersion-x86_64-linux.tar.gz -C $__ApkToolsDir + mkdir -p $__RootfsDir/usr/bin + cp -v /usr/bin/qemu-$__QEMUArch-static $__RootfsDir/usr/bin + + $__ApkToolsDir/apk-tools-$__ApkToolsVersion/apk \ + -X http://dl-cdn.alpinelinux.org/alpine/v$__AlpineVersion/main \ + -X http://dl-cdn.alpinelinux.org/alpine/v$__AlpineVersion/community \ + -U --allow-untrusted --root $__RootfsDir --arch $__AlpineArch --initdb \ + add $__AlpinePackages + + if [[ -n "$__AlpinePackagesEdgeMain" ]]; then + $__ApkToolsDir/apk-tools-$__ApkToolsVersion/apk \ + -X http://dl-cdn.alpinelinux.org/alpine/edge/main \ + -U --allow-untrusted --root $__RootfsDir --arch $__AlpineArch --initdb \ + add $__AlpinePackagesEdgeMain + fi + + if [[ -n "$__AlpinePackagesEdgeCommunity" ]]; then + $__ApkToolsDir/apk-tools-$__ApkToolsVersion/apk \ + -X http://dl-cdn.alpinelinux.org/alpine/edge/community \ + -U --allow-untrusted --root $__RootfsDir --arch $__AlpineArch --initdb \ + add $__AlpinePackagesEdgeCommunity + fi + + rm -r $__ApkToolsDir +elif [[ "$__CodeName" == "freebsd" ]]; then + mkdir -p $__RootfsDir/usr/local/etc + wget -O - https://download.freebsd.org/ftp/releases/amd64/${__FreeBSDBase}/base.txz | tar -C $__RootfsDir -Jxf - ./lib ./usr/lib ./usr/libdata ./usr/include ./usr/share/keys ./etc ./bin/freebsd-version + # For now, ask for 11 ABI even on 12. This can be revisited later. + echo "ABI = \"FreeBSD:11:amd64\"; FINGERPRINTS = \"${__RootfsDir}/usr/share/keys\"; REPOS_DIR = [\"${__RootfsDir}/etc/pkg\"]; REPO_AUTOUPDATE = NO; RUN_SCRIPTS = NO;" > ${__RootfsDir}/usr/local/etc/pkg.conf + echo "FreeBSD: { url: "pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly", mirror_type: \"srv\", signature_type: \"fingerprints\", fingerprints: \"${__RootfsDir}/usr/share/keys/pkg\", enabled: yes }" > ${__RootfsDir}/etc/pkg/FreeBSD.conf + mkdir -p $__RootfsDir/tmp + # get and build package manager + wget -O - https://github.com/freebsd/pkg/archive/${__FreeBSDPkg}.tar.gz | tar -C $__RootfsDir/tmp -zxf - + cd $__RootfsDir/tmp/pkg-${__FreeBSDPkg} + # needed for install to succeed + mkdir -p $__RootfsDir/host/etc + ./autogen.sh && ./configure --prefix=$__RootfsDir/host && make && make install + rm -rf $__RootfsDir/tmp/pkg-${__FreeBSDPkg} + # install packages we need. + INSTALL_AS_USER=$(whoami) $__RootfsDir/host/sbin/pkg -r $__RootfsDir -C $__RootfsDir/usr/local/etc/pkg.conf update + INSTALL_AS_USER=$(whoami) $__RootfsDir/host/sbin/pkg -r $__RootfsDir -C $__RootfsDir/usr/local/etc/pkg.conf install --yes $__FreeBSDPackages +elif [[ "$__CodeName" == "illumos" ]]; then + mkdir "$__RootfsDir/tmp" + pushd "$__RootfsDir/tmp" + JOBS="$(getconf _NPROCESSORS_ONLN)" + echo "Downloading sysroot." + wget -O - https://github.com/illumos/sysroot/releases/download/20181213-de6af22ae73b-v1/illumos-sysroot-i386-20181213-de6af22ae73b-v1.tar.gz | tar -C "$__RootfsDir" -xzf - + echo "Building binutils. Please wait.." + wget -O - https://ftp.gnu.org/gnu/binutils/binutils-2.33.1.tar.bz2 | tar -xjf - + mkdir build-binutils && cd build-binutils + ../binutils-2.33.1/configure --prefix="$__RootfsDir" --target="x86_64-sun-solaris2.10" --program-prefix="x86_64-illumos-" --with-sysroot="$__RootfsDir" + make -j "$JOBS" && make install && cd .. + echo "Building gcc. Please wait.." + wget -O - https://ftp.gnu.org/gnu/gcc/gcc-8.4.0/gcc-8.4.0.tar.xz | tar -xJf - + CFLAGS="-fPIC" + CXXFLAGS="-fPIC" + CXXFLAGS_FOR_TARGET="-fPIC" + CFLAGS_FOR_TARGET="-fPIC" + export CFLAGS CXXFLAGS CXXFLAGS_FOR_TARGET CFLAGS_FOR_TARGET + mkdir build-gcc && cd build-gcc + ../gcc-8.4.0/configure --prefix="$__RootfsDir" --target="x86_64-sun-solaris2.10" --program-prefix="x86_64-illumos-" --with-sysroot="$__RootfsDir" --with-gnu-as \ + --with-gnu-ld --disable-nls --disable-libgomp --disable-libquadmath --disable-libssp --disable-libvtv --disable-libcilkrts --disable-libada --disable-libsanitizer \ + --disable-libquadmath-support --disable-shared --enable-tls + make -j "$JOBS" && make install && cd .. + BaseUrl=https://pkgsrc.joyent.com + if [[ "$__UseMirror" == 1 ]]; then + BaseUrl=http://pkgsrc.smartos.skylime.net + fi + BaseUrl="$BaseUrl"/packages/SmartOS/2020Q1/x86_64/All + echo "Downloading dependencies." + read -ra array <<<"$__IllumosPackages" + for package in "${array[@]}"; do + echo "Installing $package..." + wget "$BaseUrl"/"$package".tgz + ar -x "$package".tgz + tar --skip-old-files -xzf "$package".tmp.tgz -C "$__RootfsDir" 2>/dev/null + done + echo "Cleaning up temporary files." + popd + rm -rf "$__RootfsDir"/{tmp,+*} + mkdir -p "$__RootfsDir"/usr/include/net + mkdir -p "$__RootfsDir"/usr/include/netpacket + wget -P "$__RootfsDir"/usr/include/net https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/io/bpf/net/bpf.h + wget -P "$__RootfsDir"/usr/include/net https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/io/bpf/net/dlt.h + wget -P "$__RootfsDir"/usr/include/netpacket https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/inet/sockmods/netpacket/packet.h + wget -P "$__RootfsDir"/usr/include/sys https://raw.githubusercontent.com/illumos/illumos-gate/master/usr/src/uts/common/sys/sdt.h +elif [[ -n $__CodeName ]]; then + qemu-debootstrap --arch $__UbuntuArch $__CodeName $__RootfsDir $__UbuntuRepo + cp $__CrossDir/$__BuildArch/sources.list.$__CodeName $__RootfsDir/etc/apt/sources.list + chroot $__RootfsDir apt-get update + chroot $__RootfsDir apt-get -f -y install + chroot $__RootfsDir apt-get -y install $__UbuntuPackages + chroot $__RootfsDir symlinks -cr /usr + chroot $__RootfsDir apt-get clean + + if [ $__SkipUnmount == 0 ]; then + umount $__RootfsDir/* || true + fi + + if [[ "$__BuildArch" == "arm" && "$__CodeName" == "trusty" ]]; then + pushd $__RootfsDir + patch -p1 < $__CrossDir/$__BuildArch/trusty.patch + patch -p1 < $__CrossDir/$__BuildArch/trusty-lttng-2.4.patch + popd + fi + + if [[ "$__BuildArch" == "armel" && "$__CodeName" == "jessie" ]]; then + pushd $__RootfsDir + patch -p1 < $__CrossDir/$__BuildArch/armel.jessie.patch + popd + fi +elif [[ "$__Tizen" == "tizen" ]]; then + ROOTFS_DIR=$__RootfsDir $__CrossDir/$__BuildArch/tizen-build-rootfs.sh +else + echo "Unsupported target platform." + usage; + exit 1 +fi diff --git a/eng/common/cross/s390x/sources.list.bionic b/eng/common/cross/s390x/sources.list.bionic new file mode 100644 index 000000000..210955740 --- /dev/null +++ b/eng/common/cross/s390x/sources.list.bionic @@ -0,0 +1,11 @@ +deb http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted universe + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted + +deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse +deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted universe multiverse diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake new file mode 100644 index 000000000..fc11001aa --- /dev/null +++ b/eng/common/cross/toolchain.cmake @@ -0,0 +1,242 @@ +set(CROSS_ROOTFS $ENV{ROOTFS_DIR}) + +set(TARGET_ARCH_NAME $ENV{TARGET_BUILD_ARCH}) +if(EXISTS ${CROSS_ROOTFS}/bin/freebsd-version) + set(CMAKE_SYSTEM_NAME FreeBSD) +elseif(EXISTS ${CROSS_ROOTFS}/usr/platform/i86pc) + set(CMAKE_SYSTEM_NAME SunOS) + set(ILLUMOS 1) +else() + set(CMAKE_SYSTEM_NAME Linux) +endif() +set(CMAKE_SYSTEM_VERSION 1) + +if(TARGET_ARCH_NAME STREQUAL "armel") + set(CMAKE_SYSTEM_PROCESSOR armv7l) + set(TOOLCHAIN "arm-linux-gnueabi") + if("$ENV{__DistroRid}" MATCHES "tizen.*") + set(TIZEN_TOOLCHAIN "armv7l-tizen-linux-gnueabi/9.2.0") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "arm") + set(CMAKE_SYSTEM_PROCESSOR armv7l) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv7-alpine-linux-musleabihf) + set(TOOLCHAIN "armv7-alpine-linux-musleabihf") + elseif(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/armv6-alpine-linux-musleabihf) + set(TOOLCHAIN "armv6-alpine-linux-musleabihf") + else() + set(TOOLCHAIN "arm-linux-gnueabihf") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "arm64") + set(CMAKE_SYSTEM_PROCESSOR aarch64) + if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/aarch64-alpine-linux-musl) + set(TOOLCHAIN "aarch64-alpine-linux-musl") + else() + set(TOOLCHAIN "aarch64-linux-gnu") + endif() + if("$ENV{__DistroRid}" MATCHES "tizen.*") + set(TIZEN_TOOLCHAIN "aarch64-tizen-linux-gnu/9.2.0") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "s390x") + set(CMAKE_SYSTEM_PROCESSOR s390x) + set(TOOLCHAIN "s390x-linux-gnu") +elseif(TARGET_ARCH_NAME STREQUAL "x86") + set(CMAKE_SYSTEM_PROCESSOR i686) + set(TOOLCHAIN "i686-linux-gnu") +elseif (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + set(CMAKE_SYSTEM_PROCESSOR "x86_64") + set(triple "x86_64-unknown-freebsd11") +elseif (ILLUMOS) + set(CMAKE_SYSTEM_PROCESSOR "x86_64") + set(TOOLCHAIN "x86_64-illumos") +else() + message(FATAL_ERROR "Arch is ${TARGET_ARCH_NAME}. Only armel, arm, arm64, s390x and x86 are supported!") +endif() + +if(DEFINED ENV{TOOLCHAIN}) + set(TOOLCHAIN $ENV{TOOLCHAIN}) +endif() + +# Specify include paths +if(DEFINED TIZEN_TOOLCHAIN) + if(TARGET_ARCH_NAME STREQUAL "armel") + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/) + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}/include/c++/armv7l-tizen-linux-gnueabi) + endif() + if(TARGET_ARCH_NAME STREQUAL "arm64") + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/) + include_directories(SYSTEM ${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}/include/c++/aarch64-tizen-linux-gnu) + endif() +endif() + +if("$ENV{__DistroRid}" MATCHES "android.*") + if(TARGET_ARCH_NAME STREQUAL "arm") + set(ANDROID_ABI armeabi-v7a) + elseif(TARGET_ARCH_NAME STREQUAL "arm64") + set(ANDROID_ABI arm64-v8a) + endif() + + # extract platform number required by the NDK's toolchain + string(REGEX REPLACE ".*\\.([0-9]+)-.*" "\\1" ANDROID_PLATFORM "$ENV{__DistroRid}") + + set(ANDROID_TOOLCHAIN clang) + set(FEATURE_EVENT_TRACE 0) # disable event trace as there is no lttng-ust package in termux repository + set(CMAKE_SYSTEM_LIBRARY_PATH "${CROSS_ROOTFS}/usr/lib") + set(CMAKE_SYSTEM_INCLUDE_PATH "${CROSS_ROOTFS}/usr/include") + + # include official NDK toolchain script + include(${CROSS_ROOTFS}/../build/cmake/android.toolchain.cmake) +elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + # we cross-compile by instructing clang + set(CMAKE_C_COMPILER_TARGET ${triple}) + set(CMAKE_CXX_COMPILER_TARGET ${triple}) + set(CMAKE_ASM_COMPILER_TARGET ${triple}) + set(CMAKE_SYSROOT "${CROSS_ROOTFS}") +elseif(ILLUMOS) + set(CMAKE_SYSROOT "${CROSS_ROOTFS}") + + include_directories(SYSTEM ${CROSS_ROOTFS}/include) + + set(TOOLSET_PREFIX ${TOOLCHAIN}-) + function(locate_toolchain_exec exec var) + string(TOUPPER ${exec} EXEC_UPPERCASE) + if(NOT "$ENV{CLR_${EXEC_UPPERCASE}}" STREQUAL "") + set(${var} "$ENV{CLR_${EXEC_UPPERCASE}}" PARENT_SCOPE) + return() + endif() + + find_program(EXEC_LOCATION_${exec} + NAMES + "${TOOLSET_PREFIX}${exec}${CLR_CMAKE_COMPILER_FILE_NAME_VERSION}" + "${TOOLSET_PREFIX}${exec}") + + if (EXEC_LOCATION_${exec} STREQUAL "EXEC_LOCATION_${exec}-NOTFOUND") + message(FATAL_ERROR "Unable to find toolchain executable. Name: ${exec}, Prefix: ${TOOLSET_PREFIX}.") + endif() + set(${var} ${EXEC_LOCATION_${exec}} PARENT_SCOPE) + endfunction() + + set(CMAKE_SYSTEM_PREFIX_PATH "${CROSS_ROOTFS}") + + locate_toolchain_exec(gcc CMAKE_C_COMPILER) + locate_toolchain_exec(g++ CMAKE_CXX_COMPILER) + + set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lssp") + set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp") +else() + set(CMAKE_SYSROOT "${CROSS_ROOTFS}") + + set(CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") + set(CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") + set(CMAKE_ASM_COMPILER_EXTERNAL_TOOLCHAIN "${CROSS_ROOTFS}/usr") +endif() + +# Specify link flags + +function(add_toolchain_linker_flag Flag) + set(Config "${ARGV1}") + set(CONFIG_SUFFIX "") + if (NOT Config STREQUAL "") + set(CONFIG_SUFFIX "_${Config}") + endif() + set("CMAKE_EXE_LINKER_FLAGS${CONFIG_SUFFIX}" "${CMAKE_EXE_LINKER_FLAGS${CONFIG_SUFFIX}} ${Flag}" PARENT_SCOPE) + set("CMAKE_SHARED_LINKER_FLAGS${CONFIG_SUFFIX}" "${CMAKE_SHARED_LINKER_FLAGS${CONFIG_SUFFIX}} ${Flag}" PARENT_SCOPE) +endfunction() + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/lib/${TOOLCHAIN}") + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib/${TOOLCHAIN}") +endif() + +if(TARGET_ARCH_NAME STREQUAL "armel") + if(DEFINED TIZEN_TOOLCHAIN) # For Tizen only + add_toolchain_linker_flag("-B${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib/gcc/${TIZEN_TOOLCHAIN}") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "arm64") + if(DEFINED TIZEN_TOOLCHAIN) # For Tizen only + add_toolchain_linker_flag("-B${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib64") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib64") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") + + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/lib64") + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib64") + add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}") + endif() +elseif(TARGET_ARCH_NAME STREQUAL "x86") + add_toolchain_linker_flag(-m32) +elseif(ILLUMOS) + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/lib/amd64") + add_toolchain_linker_flag("-L${CROSS_ROOTFS}/usr/amd64/lib") +endif() + +# Specify compile options + +if((TARGET_ARCH_NAME MATCHES "^(arm|armel|arm64|s390x)$" AND NOT "$ENV{__DistroRid}" MATCHES "android.*") OR ILLUMOS) + set(CMAKE_C_COMPILER_TARGET ${TOOLCHAIN}) + set(CMAKE_CXX_COMPILER_TARGET ${TOOLCHAIN}) + set(CMAKE_ASM_COMPILER_TARGET ${TOOLCHAIN}) +endif() + +if(TARGET_ARCH_NAME MATCHES "^(arm|armel)$") + add_compile_options(-mthumb) + if (NOT DEFINED CLR_ARM_FPU_TYPE) + set (CLR_ARM_FPU_TYPE vfpv3) + endif (NOT DEFINED CLR_ARM_FPU_TYPE) + + add_compile_options (-mfpu=${CLR_ARM_FPU_TYPE}) + if (NOT DEFINED CLR_ARM_FPU_CAPABILITY) + set (CLR_ARM_FPU_CAPABILITY 0x7) + endif (NOT DEFINED CLR_ARM_FPU_CAPABILITY) + + add_definitions (-DCLR_ARM_FPU_CAPABILITY=${CLR_ARM_FPU_CAPABILITY}) + + if(TARGET_ARCH_NAME STREQUAL "armel") + add_compile_options(-mfloat-abi=softfp) + endif() +elseif(TARGET_ARCH_NAME STREQUAL "x86") + add_compile_options(-m32) + add_compile_options(-Wno-error=unused-command-line-argument) +endif() + +if(DEFINED TIZEN_TOOLCHAIN) + if(TARGET_ARCH_NAME MATCHES "^(armel|arm64)$") + add_compile_options(-Wno-deprecated-declarations) # compile-time option + add_compile_options(-D__extern_always_inline=inline) # compile-time option + endif() +endif() + +# Set LLDB include and library paths for builds that need lldb. +if(TARGET_ARCH_NAME MATCHES "^(arm|armel|x86)$") + if(TARGET_ARCH_NAME STREQUAL "x86") + set(LLVM_CROSS_DIR "$ENV{LLVM_CROSS_HOME}") + else() # arm/armel case + set(LLVM_CROSS_DIR "$ENV{LLVM_ARM_HOME}") + endif() + if(LLVM_CROSS_DIR) + set(WITH_LLDB_LIBS "${LLVM_CROSS_DIR}/lib/" CACHE STRING "") + set(WITH_LLDB_INCLUDES "${LLVM_CROSS_DIR}/include" CACHE STRING "") + set(LLDB_H "${WITH_LLDB_INCLUDES}" CACHE STRING "") + set(LLDB "${LLVM_CROSS_DIR}/lib/liblldb.so" CACHE STRING "") + else() + if(TARGET_ARCH_NAME STREQUAL "x86") + set(WITH_LLDB_LIBS "${CROSS_ROOTFS}/usr/lib/i386-linux-gnu" CACHE STRING "") + set(CHECK_LLVM_DIR "${CROSS_ROOTFS}/usr/lib/llvm-3.8/include") + if(EXISTS "${CHECK_LLVM_DIR}" AND IS_DIRECTORY "${CHECK_LLVM_DIR}") + set(WITH_LLDB_INCLUDES "${CHECK_LLVM_DIR}") + else() + set(WITH_LLDB_INCLUDES "${CROSS_ROOTFS}/usr/lib/llvm-3.6/include") + endif() + else() # arm/armel case + set(WITH_LLDB_LIBS "${CROSS_ROOTFS}/usr/lib/${TOOLCHAIN}" CACHE STRING "") + set(WITH_LLDB_INCLUDES "${CROSS_ROOTFS}/usr/lib/llvm-3.6/include" CACHE STRING "") + endif() + endif() +endif() + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/eng/common/cross/x86/sources.list.bionic b/eng/common/cross/x86/sources.list.bionic new file mode 100644 index 000000000..a71ccadcf --- /dev/null +++ b/eng/common/cross/x86/sources.list.bionic @@ -0,0 +1,11 @@ +deb http://archive.ubuntu.com/ubuntu/ bionic main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ bionic main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ bionic-backports main restricted +deb-src http://archive.ubuntu.com/ubuntu/ bionic-backports main restricted + +deb http://archive.ubuntu.com/ubuntu/ bionic-security main restricted universe multiverse +deb-src http://archive.ubuntu.com/ubuntu/ bionic-security main restricted universe multiverse diff --git a/eng/common/cross/x86/sources.list.trusty b/eng/common/cross/x86/sources.list.trusty new file mode 100644 index 000000000..9b3085436 --- /dev/null +++ b/eng/common/cross/x86/sources.list.trusty @@ -0,0 +1,11 @@ +deb http://archive.ubuntu.com/ubuntu/ trusty main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ trusty main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ trusty-updates main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ trusty-updates main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ trusty-backports main restricted +deb-src http://archive.ubuntu.com/ubuntu/ trusty-backports main restricted + +deb http://archive.ubuntu.com/ubuntu/ trusty-security main restricted universe multiverse +deb-src http://archive.ubuntu.com/ubuntu/ trusty-security main restricted universe multiverse diff --git a/eng/common/cross/x86/sources.list.xenial b/eng/common/cross/x86/sources.list.xenial new file mode 100644 index 000000000..ad9c5a014 --- /dev/null +++ b/eng/common/cross/x86/sources.list.xenial @@ -0,0 +1,11 @@ +deb http://archive.ubuntu.com/ubuntu/ xenial main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ xenial main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ xenial-updates main restricted universe +deb-src http://archive.ubuntu.com/ubuntu/ xenial-updates main restricted universe + +deb http://archive.ubuntu.com/ubuntu/ xenial-backports main restricted +deb-src http://archive.ubuntu.com/ubuntu/ xenial-backports main restricted + +deb http://archive.ubuntu.com/ubuntu/ xenial-security main restricted universe multiverse +deb-src http://archive.ubuntu.com/ubuntu/ xenial-security main restricted universe multiverse diff --git a/eng/common/darc-init.ps1 b/eng/common/darc-init.ps1 new file mode 100644 index 000000000..435e76413 --- /dev/null +++ b/eng/common/darc-init.ps1 @@ -0,0 +1,47 @@ +param ( + $darcVersion = $null, + $versionEndpoint = 'https://maestro-prod.westus2.cloudapp.azure.com/api/assets/darc-version?api-version=2019-01-16', + $verbosity = 'minimal', + $toolpath = $null +) + +. $PSScriptRoot\tools.ps1 + +function InstallDarcCli ($darcVersion, $toolpath) { + $darcCliPackageName = 'microsoft.dotnet.darc' + + $dotnetRoot = InitializeDotNetCli -install:$true + $dotnet = "$dotnetRoot\dotnet.exe" + $toolList = & "$dotnet" tool list -g + + if ($toolList -like "*$darcCliPackageName*") { + & "$dotnet" tool uninstall $darcCliPackageName -g + } + + # If the user didn't explicitly specify the darc version, + # query the Maestro API for the correct version of darc to install. + if (-not $darcVersion) { + $darcVersion = $(Invoke-WebRequest -Uri $versionEndpoint -UseBasicParsing).Content + } + + $arcadeServicesSource = 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + + Write-Host "Installing Darc CLI version $darcVersion..." + Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' + if (-not $toolpath) { + Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity -g" + & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g + }else { + Write-Host "'$dotnet' tool install $darcCliPackageName --version $darcVersion --add-source '$arcadeServicesSource' -v $verbosity --tool-path '$toolpath'" + & "$dotnet" tool install $darcCliPackageName --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath" + } +} + +try { + InstallDarcCli $darcVersion $toolpath +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Darc' -Message $_ + ExitWithExitCode 1 +} \ No newline at end of file diff --git a/eng/common/darc-init.sh b/eng/common/darc-init.sh new file mode 100755 index 000000000..39abdbecd --- /dev/null +++ b/eng/common/darc-init.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +darcVersion='' +versionEndpoint='https://maestro-prod.westus2.cloudapp.azure.com/api/assets/darc-version?api-version=2019-01-16' +verbosity='minimal' + +while [[ $# > 0 ]]; do + opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + --darcversion) + darcVersion=$2 + shift + ;; + --versionendpoint) + versionEndpoint=$2 + shift + ;; + --verbosity) + verbosity=$2 + shift + ;; + --toolpath) + toolpath=$2 + shift + ;; + *) + echo "Invalid argument: $1" + usage + exit 1 + ;; + esac + + shift +done + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. "$scriptroot/tools.sh" + +if [ -z "$darcVersion" ]; then + darcVersion=$(curl -X GET "$versionEndpoint" -H "accept: text/plain") +fi + +function InstallDarcCli { + local darc_cli_package_name="microsoft.dotnet.darc" + + InitializeDotNetCli + local dotnet_root=$_InitializeDotNetCli + + if [ -z "$toolpath" ]; then + local tool_list=$($dotnet_root/dotnet tool list -g) + if [[ $tool_list = *$darc_cli_package_name* ]]; then + echo $($dotnet_root/dotnet tool uninstall $darc_cli_package_name -g) + fi + else + local tool_list=$($dotnet_root/dotnet tool list --tool-path "$toolpath") + if [[ $tool_list = *$darc_cli_package_name* ]]; then + echo $($dotnet_root/dotnet tool uninstall $darc_cli_package_name --tool-path "$toolpath") + fi + fi + + local arcadeServicesSource="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" + + echo "Installing Darc CLI version $darcVersion..." + echo "You may need to restart your command shell if this is the first dotnet tool you have installed." + if [ -z "$toolpath" ]; then + echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity -g) + else + echo $($dotnet_root/dotnet tool install $darc_cli_package_name --version $darcVersion --add-source "$arcadeServicesSource" -v $verbosity --tool-path "$toolpath") + fi +} + +InstallDarcCli diff --git a/eng/common/dotnet-install.cmd b/eng/common/dotnet-install.cmd new file mode 100644 index 000000000..b1c2642e7 --- /dev/null +++ b/eng/common/dotnet-install.cmd @@ -0,0 +1,2 @@ +@echo off +powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0dotnet-install.ps1""" %*" \ No newline at end of file diff --git a/eng/common/dotnet-install.ps1 b/eng/common/dotnet-install.ps1 new file mode 100644 index 000000000..811f0f717 --- /dev/null +++ b/eng/common/dotnet-install.ps1 @@ -0,0 +1,28 @@ +[CmdletBinding(PositionalBinding=$false)] +Param( + [string] $verbosity = 'minimal', + [string] $architecture = '', + [string] $version = 'Latest', + [string] $runtime = 'dotnet', + [string] $RuntimeSourceFeed = '', + [string] $RuntimeSourceFeedKey = '' +) + +. $PSScriptRoot\tools.ps1 + +$dotnetRoot = Join-Path $RepoRoot '.dotnet' + +$installdir = $dotnetRoot +try { + if ($architecture -and $architecture.Trim() -eq 'x86') { + $installdir = Join-Path $installdir 'x86' + } + InstallDotNet $installdir $version $architecture $runtime $true -RuntimeSourceFeed $RuntimeSourceFeed -RuntimeSourceFeedKey $RuntimeSourceFeedKey +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ + ExitWithExitCode 1 +} + +ExitWithExitCode 0 diff --git a/eng/common/dotnet-install.sh b/eng/common/dotnet-install.sh new file mode 100755 index 000000000..d6efeb443 --- /dev/null +++ b/eng/common/dotnet-install.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. "$scriptroot/tools.sh" + +version='Latest' +architecture='' +runtime='dotnet' +runtimeSourceFeed='' +runtimeSourceFeedKey='' +while [[ $# > 0 ]]; do + opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -version|-v) + shift + version="$1" + ;; + -architecture|-a) + shift + architecture="$1" + ;; + -runtime|-r) + shift + runtime="$1" + ;; + -runtimesourcefeed) + shift + runtimeSourceFeed="$1" + ;; + -runtimesourcefeedkey) + shift + runtimeSourceFeedKey="$1" + ;; + *) + Write-PipelineTelemetryError -Category 'Build' -Message "Invalid argument: $1" + exit 1 + ;; + esac + shift +done + +# Use uname to determine what the CPU is, see https://en.wikipedia.org/wiki/Uname#Examples +cpuname=$(uname -m) +case $cpuname in + aarch64) + buildarch=arm64 + ;; + amd64|x86_64) + buildarch=x64 + ;; + armv*l) + buildarch=arm + ;; + i686) + buildarch=x86 + ;; + *) + echo "Unknown CPU $cpuname detected, treating it as x64" + buildarch=x64 + ;; +esac + +dotnetRoot="$repo_root/.dotnet" +if [[ $architecture != "" ]] && [[ $architecture != $buildarch ]]; then + dotnetRoot="$dotnetRoot/$architecture" +fi + +InstallDotNet $dotnetRoot $version "$architecture" $runtime true $runtimeSourceFeed $runtimeSourceFeedKey || { + local exit_code=$? + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "dotnet-install.sh failed (exit code '$exit_code')." >&2 + ExitWithExitCode $exit_code +} + +ExitWithExitCode 0 diff --git a/eng/common/enable-cross-org-publishing.ps1 b/eng/common/enable-cross-org-publishing.ps1 new file mode 100644 index 000000000..da09da4f1 --- /dev/null +++ b/eng/common/enable-cross-org-publishing.ps1 @@ -0,0 +1,13 @@ +param( + [string] $token +) + + +. $PSScriptRoot\pipeline-logging-functions.ps1 + +# Write-PipelineSetVariable will no-op if a variable named $ci is not defined +# Since this script is only ever called in AzDO builds, just universally set it +$ci = $true + +Write-PipelineSetVariable -Name 'VSS_NUGET_ACCESSTOKEN' -Value $token -IsMultiJobVariable $false +Write-PipelineSetVariable -Name 'VSS_NUGET_URI_PREFIXES' -Value 'https://dnceng.pkgs.visualstudio.com/;https://pkgs.dev.azure.com/dnceng/;https://devdiv.pkgs.visualstudio.com/;https://pkgs.dev.azure.com/devdiv/' -IsMultiJobVariable $false diff --git a/eng/common/generate-graph-files.ps1 b/eng/common/generate-graph-files.ps1 new file mode 100644 index 000000000..0728b1a8b --- /dev/null +++ b/eng/common/generate-graph-files.ps1 @@ -0,0 +1,86 @@ +Param( + [Parameter(Mandatory=$true)][string] $barToken, # Token generated at https://maestro-prod.westus2.cloudapp.azure.com/Account/Tokens + [Parameter(Mandatory=$true)][string] $gitHubPat, # GitHub personal access token from https://github.com/settings/tokens (no auth scopes needed) + [Parameter(Mandatory=$true)][string] $azdoPat, # Azure Dev Ops tokens from https://dev.azure.com/dnceng/_details/security/tokens (code read scope needed) + [Parameter(Mandatory=$true)][string] $outputFolder, # Where the graphviz.txt file will be created + [string] $darcVersion, # darc's version + [string] $graphvizVersion = '2.38', # GraphViz version + [switch] $includeToolset # Whether the graph should include toolset dependencies or not. i.e. arcade, optimization. For more about + # toolset dependencies see https://github.com/dotnet/arcade/blob/master/Documentation/Darc.md#toolset-vs-product-dependencies +) + +function CheckExitCode ([string]$stage) +{ + $exitCode = $LASTEXITCODE + if ($exitCode -ne 0) { + Write-PipelineTelemetryError -Category 'Arcade' -Message "Something failed in stage: '$stage'. Check for errors above. Exiting now..." + ExitWithExitCode $exitCode + } +} + +try { + $ErrorActionPreference = 'Stop' + . $PSScriptRoot\tools.ps1 + + Import-Module -Name (Join-Path $PSScriptRoot 'native\CommonLibrary.psm1') + + Push-Location $PSScriptRoot + + Write-Host 'Installing darc...' + . .\darc-init.ps1 -darcVersion $darcVersion + CheckExitCode 'Running darc-init' + + $engCommonBaseDir = Join-Path $PSScriptRoot 'native\' + $graphvizInstallDir = CommonLibrary\Get-NativeInstallDirectory + $nativeToolBaseUri = 'https://netcorenativeassets.blob.core.windows.net/resource-packages/external' + $installBin = Join-Path $graphvizInstallDir 'bin' + + Write-Host 'Installing dot...' + .\native\install-tool.ps1 -ToolName graphviz -InstallPath $installBin -BaseUri $nativeToolBaseUri -CommonLibraryDirectory $engCommonBaseDir -Version $graphvizVersion -Verbose + + $darcExe = "$env:USERPROFILE\.dotnet\tools" + $darcExe = Resolve-Path "$darcExe\darc.exe" + + Create-Directory $outputFolder + + # Generate 3 graph descriptions: + # 1. Flat with coherency information + # 2. Graphviz (dot) file + # 3. Standard dependency graph + $graphVizFilePath = "$outputFolder\graphviz.txt" + $graphVizImageFilePath = "$outputFolder\graph.png" + $normalGraphFilePath = "$outputFolder\graph-full.txt" + $flatGraphFilePath = "$outputFolder\graph-flat.txt" + $baseOptions = @( '--github-pat', "$gitHubPat", '--azdev-pat', "$azdoPat", '--password', "$barToken" ) + + if ($includeToolset) { + Write-Host 'Toolsets will be included in the graph...' + $baseOptions += @( '--include-toolset' ) + } + + Write-Host 'Generating standard dependency graph...' + & "$darcExe" get-dependency-graph @baseOptions --output-file $normalGraphFilePath + CheckExitCode 'Generating normal dependency graph' + + Write-Host 'Generating flat dependency graph and graphviz file...' + & "$darcExe" get-dependency-graph @baseOptions --flat --coherency --graphviz $graphVizFilePath --output-file $flatGraphFilePath + CheckExitCode 'Generating flat and graphviz dependency graph' + + Write-Host "Generating graph image $graphVizFilePath" + $dotFilePath = Join-Path $installBin "graphviz\$graphvizVersion\release\bin\dot.exe" + & "$dotFilePath" -Tpng -o"$graphVizImageFilePath" "$graphVizFilePath" + CheckExitCode 'Generating graphviz image' + + Write-Host "'$graphVizFilePath', '$flatGraphFilePath', '$normalGraphFilePath' and '$graphVizImageFilePath' created!" +} +catch { + if (!$includeToolset) { + Write-Host 'This might be a toolset repo which includes only toolset dependencies. ' -NoNewline -ForegroundColor Yellow + Write-Host 'Since -includeToolset is not set there is no graph to create. Include -includeToolset and try again...' -ForegroundColor Yellow + } + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Arcade' -Message $_ + ExitWithExitCode 1 +} finally { + Pop-Location +} \ No newline at end of file diff --git a/eng/common/generate-locproject.ps1 b/eng/common/generate-locproject.ps1 new file mode 100644 index 000000000..de348a2e2 --- /dev/null +++ b/eng/common/generate-locproject.ps1 @@ -0,0 +1,110 @@ +Param( + [Parameter(Mandatory=$true)][string] $SourcesDirectory, # Directory where source files live; if using a Localize directory it should live in here + [string] $LanguageSet = 'VS_Main_Languages', # Language set to be used in the LocProject.json + [switch] $UseCheckedInLocProjectJson, # When set, generates a LocProject.json and compares it to one that already exists in the repo; otherwise just generates one + [switch] $CreateNeutralXlfs # Creates neutral xlf files. Only set to false when running locally +) + +# Generates LocProject.json files for the OneLocBuild task. OneLocBuildTask is described here: +# https://ceapex.visualstudio.com/CEINTL/_wiki/wikis/CEINTL.wiki/107/Localization-with-OneLocBuild-Task + +Set-StrictMode -Version 2.0 +$ErrorActionPreference = "Stop" +. $PSScriptRoot\tools.ps1 + +Import-Module -Name (Join-Path $PSScriptRoot 'native\CommonLibrary.psm1') + +$exclusionsFilePath = "$SourcesDirectory\eng\Localize\LocExclusions.json" +$exclusions = @{ Exclusions = @() } +if (Test-Path -Path $exclusionsFilePath) +{ + $exclusions = Get-Content "$exclusionsFilePath" | ConvertFrom-Json +} + +Push-Location "$SourcesDirectory" # push location for Resolve-Path -Relative to work + +# Template files +$jsonFiles = @() +$jsonFiles += Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "\.template\.config\\localize\\en\..+\.json" } # .NET templating pattern +$jsonFiles += Get-ChildItem -Recurse -Path "$SourcesDirectory" | Where-Object { $_.FullName -Match "en\\strings\.json" } # current winforms pattern + +$xlfFiles = @() + +$allXlfFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory\*\*.xlf" +$langXlfFiles = @() +if ($allXlfFiles) { + $null = $allXlfFiles[0].FullName -Match "\.([\w-]+)\.xlf" # matches '[langcode].xlf' + $firstLangCode = $Matches.1 + $langXlfFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory\*\*.$firstLangCode.xlf" +} +$langXlfFiles | ForEach-Object { + $null = $_.Name -Match "(.+)\.[\w-]+\.xlf" # matches '[filename].[langcode].xlf + + $destinationFile = "$($_.Directory.FullName)\$($Matches.1).xlf" + $xlfFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru +} + +$locFiles = $jsonFiles + $xlfFiles + +$locJson = @{ + Projects = @( + @{ + LanguageSet = $LanguageSet + LocItems = @( + $locFiles | ForEach-Object { + $outputPath = "$(($_.DirectoryName | Resolve-Path -Relative) + "\")" + $continue = $true + foreach ($exclusion in $exclusions.Exclusions) { + if ($outputPath.Contains($exclusion)) + { + $continue = $false + } + } + $sourceFile = ($_.FullName | Resolve-Path -Relative) + if (!$CreateNeutralXlfs -and $_.Extension -eq '.xlf') { + Remove-Item -Path $sourceFile + } + if ($continue) + { + if ($_.Directory.Name -eq 'en' -and $_.Extension -eq '.json') { + return @{ + SourceFile = $sourceFile + CopyOption = "LangIDOnPath" + OutputPath = "$($_.Directory.Parent.FullName | Resolve-Path -Relative)\" + } + } + else { + return @{ + SourceFile = $sourceFile + CopyOption = "LangIDOnName" + OutputPath = $outputPath + } + } + } + } + ) + } + ) +} + +$json = ConvertTo-Json $locJson -Depth 5 +Write-Host "LocProject.json generated:`n`n$json`n`n" +Pop-Location + +if (!$UseCheckedInLocProjectJson) { + New-Item "$SourcesDirectory\eng\Localize\LocProject.json" -Force # Need this to make sure the Localize directory is created + Set-Content "$SourcesDirectory\eng\Localize\LocProject.json" $json +} +else { + New-Item "$SourcesDirectory\eng\Localize\LocProject-generated.json" -Force # Need this to make sure the Localize directory is created + Set-Content "$SourcesDirectory\eng\Localize\LocProject-generated.json" $json + + if ((Get-FileHash "$SourcesDirectory\eng\Localize\LocProject-generated.json").Hash -ne (Get-FileHash "$SourcesDirectory\eng\Localize\LocProject.json").Hash) { + Write-PipelineTelemetryError -Category "OneLocBuild" -Message "Existing LocProject.json differs from generated LocProject.json. Download LocProject-generated.json and compare them." + + exit 1 + } + else { + Write-Host "Generated LocProject.json and current LocProject.json are identical." + } +} \ No newline at end of file diff --git a/eng/common/helixpublish.proj b/eng/common/helixpublish.proj new file mode 100644 index 000000000..d7f185856 --- /dev/null +++ b/eng/common/helixpublish.proj @@ -0,0 +1,26 @@ + + + + msbuild + + + + + %(Identity) + + + + + + $(WorkItemDirectory) + $(WorkItemCommand) + $(WorkItemTimeout) + + + + + + + + + diff --git a/eng/common/init-tools-native.cmd b/eng/common/init-tools-native.cmd new file mode 100644 index 000000000..438cd548c --- /dev/null +++ b/eng/common/init-tools-native.cmd @@ -0,0 +1,3 @@ +@echo off +powershell -NoProfile -NoLogo -ExecutionPolicy ByPass -command "& """%~dp0init-tools-native.ps1""" %*" +exit /b %ErrorLevel% \ No newline at end of file diff --git a/eng/common/init-tools-native.ps1 b/eng/common/init-tools-native.ps1 new file mode 100644 index 000000000..db830c00a --- /dev/null +++ b/eng/common/init-tools-native.ps1 @@ -0,0 +1,152 @@ +<# +.SYNOPSIS +Entry point script for installing native tools + +.DESCRIPTION +Reads $RepoRoot\global.json file to determine native assets to install +and executes installers for those tools + +.PARAMETER BaseUri +Base file directory or Url from which to acquire tool archives + +.PARAMETER InstallDirectory +Directory to install native toolset. This is a command-line override for the default +Install directory precedence order: +- InstallDirectory command-line override +- NETCOREENG_INSTALL_DIRECTORY environment variable +- (default) %USERPROFILE%/.netcoreeng/native + +.PARAMETER Clean +Switch specifying to not install anything, but cleanup native asset folders + +.PARAMETER Force +Clean and then install tools + +.PARAMETER DownloadRetries +Total number of retry attempts + +.PARAMETER RetryWaitTimeInSeconds +Wait time between retry attempts in seconds + +.PARAMETER GlobalJsonFile +File path to global.json file + +.NOTES +#> +[CmdletBinding(PositionalBinding=$false)] +Param ( + [string] $BaseUri = 'https://netcorenativeassets.blob.core.windows.net/resource-packages/external', + [string] $InstallDirectory, + [switch] $Clean = $False, + [switch] $Force = $False, + [int] $DownloadRetries = 5, + [int] $RetryWaitTimeInSeconds = 30, + [string] $GlobalJsonFile +) + +if (!$GlobalJsonFile) { + $GlobalJsonFile = Join-Path (Get-Item $PSScriptRoot).Parent.Parent.FullName 'global.json' +} + +Set-StrictMode -version 2.0 +$ErrorActionPreference='Stop' + +. $PSScriptRoot\pipeline-logging-functions.ps1 +Import-Module -Name (Join-Path $PSScriptRoot 'native\CommonLibrary.psm1') + +try { + # Define verbose switch if undefined + $Verbose = $VerbosePreference -Eq 'Continue' + + $EngCommonBaseDir = Join-Path $PSScriptRoot 'native\' + $NativeBaseDir = $InstallDirectory + if (!$NativeBaseDir) { + $NativeBaseDir = CommonLibrary\Get-NativeInstallDirectory + } + $Env:CommonLibrary_NativeInstallDir = $NativeBaseDir + $InstallBin = Join-Path $NativeBaseDir 'bin' + $InstallerPath = Join-Path $EngCommonBaseDir 'install-tool.ps1' + + # Process tools list + Write-Host "Processing $GlobalJsonFile" + If (-Not (Test-Path $GlobalJsonFile)) { + Write-Host "Unable to find '$GlobalJsonFile'" + exit 0 + } + $NativeTools = Get-Content($GlobalJsonFile) -Raw | + ConvertFrom-Json | + Select-Object -Expand 'native-tools' -ErrorAction SilentlyContinue + if ($NativeTools) { + $NativeTools.PSObject.Properties | ForEach-Object { + $ToolName = $_.Name + $ToolVersion = $_.Value + $LocalInstallerArguments = @{ ToolName = "$ToolName" } + $LocalInstallerArguments += @{ InstallPath = "$InstallBin" } + $LocalInstallerArguments += @{ BaseUri = "$BaseUri" } + $LocalInstallerArguments += @{ CommonLibraryDirectory = "$EngCommonBaseDir" } + $LocalInstallerArguments += @{ Version = "$ToolVersion" } + + if ($Verbose) { + $LocalInstallerArguments += @{ Verbose = $True } + } + if (Get-Variable 'Force' -ErrorAction 'SilentlyContinue') { + if($Force) { + $LocalInstallerArguments += @{ Force = $True } + } + } + if ($Clean) { + $LocalInstallerArguments += @{ Clean = $True } + } + + Write-Verbose "Installing $ToolName version $ToolVersion" + Write-Verbose "Executing '$InstallerPath $($LocalInstallerArguments.Keys.ForEach({"-$_ '$($LocalInstallerArguments.$_)'"}) -join ' ')'" + & $InstallerPath @LocalInstallerArguments + if ($LASTEXITCODE -Ne "0") { + $errMsg = "$ToolName installation failed" + if ((Get-Variable 'DoNotAbortNativeToolsInstallationOnFailure' -ErrorAction 'SilentlyContinue') -and $DoNotAbortNativeToolsInstallationOnFailure) { + $showNativeToolsWarning = $true + if ((Get-Variable 'DoNotDisplayNativeToolsInstallationWarnings' -ErrorAction 'SilentlyContinue') -and $DoNotDisplayNativeToolsInstallationWarnings) { + $showNativeToolsWarning = $false + } + if ($showNativeToolsWarning) { + Write-Warning $errMsg + } + $toolInstallationFailure = $true + } else { + # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 + Write-Host $errMsg + exit 1 + } + } + } + + if ((Get-Variable 'toolInstallationFailure' -ErrorAction 'SilentlyContinue') -and $toolInstallationFailure) { + # We cannot change this to Write-PipelineTelemetryError because of https://github.com/dotnet/arcade/issues/4482 + Write-Host 'Native tools bootstrap failed' + exit 1 + } + } + else { + Write-Host 'No native tools defined in global.json' + exit 0 + } + + if ($Clean) { + exit 0 + } + if (Test-Path $InstallBin) { + Write-Host 'Native tools are available from ' (Convert-Path -Path $InstallBin) + Write-Host "##vso[task.prependpath]$(Convert-Path -Path $InstallBin)" + return $InstallBin + } + else { + Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message 'Native tools install directory does not exist, installation failed' + exit 1 + } + exit 0 +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'NativeToolsBootstrap' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/init-tools-native.sh b/eng/common/init-tools-native.sh new file mode 100755 index 000000000..5bd205b5d --- /dev/null +++ b/eng/common/init-tools-native.sh @@ -0,0 +1,238 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +base_uri='https://netcorenativeassets.blob.core.windows.net/resource-packages/external' +install_directory='' +clean=false +force=false +download_retries=5 +retry_wait_time_seconds=30 +global_json_file="$(dirname "$(dirname "${scriptroot}")")/global.json" +declare -A native_assets + +. $scriptroot/pipeline-logging-functions.sh +. $scriptroot/native/common-library.sh + +while (($# > 0)); do + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" + case $lowerI in + --baseuri) + base_uri=$2 + shift 2 + ;; + --installdirectory) + install_directory=$2 + shift 2 + ;; + --clean) + clean=true + shift 1 + ;; + --force) + force=true + shift 1 + ;; + --donotabortonfailure) + donotabortonfailure=true + shift 1 + ;; + --donotdisplaywarnings) + donotdisplaywarnings=true + shift 1 + ;; + --downloadretries) + download_retries=$2 + shift 2 + ;; + --retrywaittimeseconds) + retry_wait_time_seconds=$2 + shift 2 + ;; + --help) + echo "Common settings:" + echo " --installdirectory Directory to install native toolset." + echo " This is a command-line override for the default" + echo " Install directory precedence order:" + echo " - InstallDirectory command-line override" + echo " - NETCOREENG_INSTALL_DIRECTORY environment variable" + echo " - (default) %USERPROFILE%/.netcoreeng/native" + echo "" + echo " --clean Switch specifying not to install anything, but cleanup native asset folders" + echo " --donotabortonfailure Switch specifiying whether to abort native tools installation on failure" + echo " --donotdisplaywarnings Switch specifiying whether to display warnings during native tools installation on failure" + echo " --force Clean and then install tools" + echo " --help Print help and exit" + echo "" + echo "Advanced settings:" + echo " --baseuri Base URI for where to download native tools from" + echo " --downloadretries Number of times a download should be attempted" + echo " --retrywaittimeseconds Wait time between download attempts" + echo "" + exit 0 + ;; + esac +done + +function ReadGlobalJsonNativeTools { + # happy path: we have a proper JSON parsing tool `jq(1)` in PATH! + if command -v jq &> /dev/null; then + + # jq: read each key/value pair under "native-tools" entry and emit: + # KEY="" VALUE="" + # followed by a null byte. + # + # bash: read line with null byte delimeter and push to array (for later `eval`uation). + + while IFS= read -rd '' line; do + native_assets+=("$line") + done < <(jq -r '. | + select(has("native-tools")) | + ."native-tools" | + keys[] as $k | + @sh "KEY=\($k) VALUE=\(.[$k])\u0000"' "$global_json_file") + + return + fi + + # Warning: falling back to manually parsing JSON, which is not recommended. + + # Following routine matches the output and escaping logic of jq(1)'s @sh formatter used above. + # It has been tested with several weird strings with escaped characters in entries (key and value) + # and results were compared with the output of jq(1) in binary representation using xxd(1); + # just before the assignment to 'native_assets' array (above and below). + + # try to capture the section under "native-tools". + if [[ ! "$(cat "$global_json_file")" =~ \"native-tools\"[[:space:]\:\{]*([^\}]+) ]]; then + return + fi + + section="${BASH_REMATCH[1]}" + + parseStarted=0 + possibleEnd=0 + escaping=0 + escaped=0 + isKey=1 + + for (( i=0; i<${#section}; i++ )); do + char="${section:$i:1}" + if ! ((parseStarted)) && [[ "$char" =~ [[:space:],:] ]]; then continue; fi + + if ! ((escaping)) && [[ "$char" == "\\" ]]; then + escaping=1 + elif ((escaping)) && ! ((escaped)); then + escaped=1 + fi + + if ! ((parseStarted)) && [[ "$char" == "\"" ]]; then + parseStarted=1 + possibleEnd=0 + elif [[ "$char" == "'" ]]; then + token="$token'\\\''" + possibleEnd=0 + elif ((escaping)) || [[ "$char" != "\"" ]]; then + token="$token$char" + possibleEnd=1 + fi + + if ((possibleEnd)) && ! ((escaping)) && [[ "$char" == "\"" ]]; then + # Use printf to unescape token to match jq(1)'s @sh formatting rules. + # do not use 'token="$(printf "$token")"' syntax, as $() eats the trailing linefeed. + printf -v token "'$token'" + + if ((isKey)); then + KEY="$token" + isKey=0 + else + line="KEY=$KEY VALUE=$token" + native_assets+=("$line") + isKey=1 + fi + + # reset for next token + parseStarted=0 + token= + elif ((escaping)) && ((escaped)); then + escaping=0 + escaped=0 + fi + done +} + +native_base_dir=$install_directory +if [[ -z $install_directory ]]; then + native_base_dir=$(GetNativeInstallDirectory) +fi + +install_bin="${native_base_dir}/bin" +installed_any=false + +ReadGlobalJsonNativeTools + +if [[ ${#native_assets[@]} -eq 0 ]]; then + echo "No native tools defined in global.json" + exit 0; +else + native_installer_dir="$scriptroot/native" + for index in "${!native_assets[@]}"; do + eval "${native_assets["$index"]}" + + installer_path="$native_installer_dir/install-$KEY.sh" + installer_command="$installer_path" + installer_command+=" --baseuri $base_uri" + installer_command+=" --installpath $install_bin" + installer_command+=" --version $VALUE" + echo $installer_command + + if [[ $force = true ]]; then + installer_command+=" --force" + fi + + if [[ $clean = true ]]; then + installer_command+=" --clean" + fi + + if [[ -a $installer_path ]]; then + $installer_command + if [[ $? != 0 ]]; then + if [[ $donotabortonfailure = true ]]; then + if [[ $donotdisplaywarnings != true ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed" + fi + else + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed" + exit 1 + fi + else + $installed_any = true + fi + else + if [[ $donotabortonfailure == true ]]; then + if [[ $donotdisplaywarnings != true ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed: no install script" + fi + else + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Execution Failed: no install script" + exit 1 + fi + fi + done +fi + +if [[ $clean = true ]]; then + exit 0 +fi + +if [[ -d $install_bin ]]; then + echo "Native tools are available from $install_bin" + echo "##vso[task.prependpath]$install_bin" +else + if [[ $installed_any = true ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Native tools install directory does not exist, installation failed" + exit 1 + fi +fi + +exit 0 diff --git a/eng/common/internal-feed-operations.ps1 b/eng/common/internal-feed-operations.ps1 new file mode 100644 index 000000000..418c09930 --- /dev/null +++ b/eng/common/internal-feed-operations.ps1 @@ -0,0 +1,132 @@ +param( + [Parameter(Mandatory=$true)][string] $Operation, + [string] $AuthToken, + [string] $CommitSha, + [string] $RepoName, + [switch] $IsFeedPrivate +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 +. $PSScriptRoot\tools.ps1 + +# Sets VSS_NUGET_EXTERNAL_FEED_ENDPOINTS based on the "darc-int-*" feeds defined in NuGet.config. This is needed +# in build agents by CredProvider to authenticate the restore requests to internal feeds as specified in +# https://github.com/microsoft/artifacts-credprovider/blob/0f53327cd12fd893d8627d7b08a2171bf5852a41/README.md#environment-variables. This should ONLY be called from identified +# internal builds +function SetupCredProvider { + param( + [string] $AuthToken + ) + + # Install the Cred Provider NuGet plugin + Write-Host 'Setting up Cred Provider NuGet plugin in the agent...' + Write-Host "Getting 'installcredprovider.ps1' from 'https://github.com/microsoft/artifacts-credprovider'..." + + $url = 'https://raw.githubusercontent.com/microsoft/artifacts-credprovider/master/helpers/installcredprovider.ps1' + + Write-Host "Writing the contents of 'installcredprovider.ps1' locally..." + Invoke-WebRequest $url -OutFile installcredprovider.ps1 + + Write-Host 'Installing plugin...' + .\installcredprovider.ps1 -Force + + Write-Host "Deleting local copy of 'installcredprovider.ps1'..." + Remove-Item .\installcredprovider.ps1 + + if (-Not("$env:USERPROFILE\.nuget\plugins\netcore")) { + Write-PipelineTelemetryError -Category 'Arcade' -Message 'CredProvider plugin was not installed correctly!' + ExitWithExitCode 1 + } + else { + Write-Host 'CredProvider plugin was installed correctly!' + } + + # Then, we set the 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' environment variable to restore from the stable + # feeds successfully + + $nugetConfigPath = "$RepoRoot\NuGet.config" + + if (-Not (Test-Path -Path $nugetConfigPath)) { + Write-PipelineTelemetryError -Category 'Build' -Message 'NuGet.config file not found in repo root!' + ExitWithExitCode 1 + } + + $endpoints = New-Object System.Collections.ArrayList + $nugetConfigPackageSources = Select-Xml -Path $nugetConfigPath -XPath "//packageSources/add[contains(@key, 'darc-int-')]/@value" | foreach{$_.Node.Value} + + if (($nugetConfigPackageSources | Measure-Object).Count -gt 0 ) { + foreach ($stableRestoreResource in $nugetConfigPackageSources) { + $trimmedResource = ([string]$stableRestoreResource).Trim() + [void]$endpoints.Add(@{endpoint="$trimmedResource"; password="$AuthToken"}) + } + } + + if (($endpoints | Measure-Object).Count -gt 0) { + $endpointCredentials = @{endpointCredentials=$endpoints} | ConvertTo-Json -Compress + + # Create the environment variables the AzDo way + Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data $endpointCredentials -Properties @{ + 'variable' = 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' + 'issecret' = 'false' + } + + # We don't want sessions cached since we will be updating the endpoints quite frequently + Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data 'False' -Properties @{ + 'variable' = 'NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED' + 'issecret' = 'false' + } + } + else + { + Write-Host 'No internal endpoints found in NuGet.config' + } +} + +#Workaround for https://github.com/microsoft/msbuild/issues/4430 +function InstallDotNetSdkAndRestoreArcade { + $dotnetTempDir = "$RepoRoot\dotnet" + $dotnetSdkVersion="2.1.507" # After experimentation we know this version works when restoring the SDK (compared to 3.0.*) + $dotnet = "$dotnetTempDir\dotnet.exe" + $restoreProjPath = "$PSScriptRoot\restore.proj" + + Write-Host "Installing dotnet SDK version $dotnetSdkVersion to restore Arcade SDK..." + InstallDotNetSdk "$dotnetTempDir" "$dotnetSdkVersion" + + '' | Out-File "$restoreProjPath" + + & $dotnet restore $restoreProjPath + + Write-Host 'Arcade SDK restored!' + + if (Test-Path -Path $restoreProjPath) { + Remove-Item $restoreProjPath + } + + if (Test-Path -Path $dotnetTempDir) { + Remove-Item $dotnetTempDir -Recurse + } +} + +try { + Push-Location $PSScriptRoot + + if ($Operation -like 'setup') { + SetupCredProvider $AuthToken + } + elseif ($Operation -like 'install-restore') { + InstallDotNetSdkAndRestoreArcade + } + else { + Write-PipelineTelemetryError -Category 'Arcade' -Message "Unknown operation '$Operation'!" + ExitWithExitCode 1 + } +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Arcade' -Message $_ + ExitWithExitCode 1 +} +finally { + Pop-Location +} diff --git a/eng/common/internal-feed-operations.sh b/eng/common/internal-feed-operations.sh new file mode 100755 index 000000000..e2233e781 --- /dev/null +++ b/eng/common/internal-feed-operations.sh @@ -0,0 +1,141 @@ +#!/usr/bin/env bash + +set -e + +# Sets VSS_NUGET_EXTERNAL_FEED_ENDPOINTS based on the "darc-int-*" feeds defined in NuGet.config. This is needed +# in build agents by CredProvider to authenticate the restore requests to internal feeds as specified in +# https://github.com/microsoft/artifacts-credprovider/blob/0f53327cd12fd893d8627d7b08a2171bf5852a41/README.md#environment-variables. +# This should ONLY be called from identified internal builds +function SetupCredProvider { + local authToken=$1 + + # Install the Cred Provider NuGet plugin + echo "Setting up Cred Provider NuGet plugin in the agent..."... + echo "Getting 'installcredprovider.ps1' from 'https://github.com/microsoft/artifacts-credprovider'..." + + local url="https://raw.githubusercontent.com/microsoft/artifacts-credprovider/master/helpers/installcredprovider.sh" + + echo "Writing the contents of 'installcredprovider.ps1' locally..." + local installcredproviderPath="installcredprovider.sh" + if command -v curl > /dev/null; then + curl $url > "$installcredproviderPath" + else + wget -q -O "$installcredproviderPath" "$url" + fi + + echo "Installing plugin..." + . "$installcredproviderPath" + + echo "Deleting local copy of 'installcredprovider.sh'..." + rm installcredprovider.sh + + if [ ! -d "$HOME/.nuget/plugins" ]; then + Write-PipelineTelemetryError -category 'Build' 'CredProvider plugin was not installed correctly!' + ExitWithExitCode 1 + else + echo "CredProvider plugin was installed correctly!" + fi + + # Then, we set the 'VSS_NUGET_EXTERNAL_FEED_ENDPOINTS' environment variable to restore from the stable + # feeds successfully + + local nugetConfigPath="$repo_root/NuGet.config" + + if [ ! "$nugetConfigPath" ]; then + Write-PipelineTelemetryError -category 'Build' "NuGet.config file not found in repo's root!" + ExitWithExitCode 1 + fi + + local endpoints='[' + local nugetConfigPackageValues=`cat "$nugetConfigPath" | grep "key=\"darc-int-"` + local pattern="value=\"(.*)\"" + + for value in $nugetConfigPackageValues + do + if [[ $value =~ $pattern ]]; then + local endpoint="${BASH_REMATCH[1]}" + endpoints+="{\"endpoint\": \"$endpoint\", \"password\": \"$authToken\"}," + fi + done + + endpoints=${endpoints%?} + endpoints+=']' + + if [ ${#endpoints} -gt 2 ]; then + local endpointCredentials="{\"endpointCredentials\": "$endpoints"}" + + echo "##vso[task.setvariable variable=VSS_NUGET_EXTERNAL_FEED_ENDPOINTS]$endpointCredentials" + echo "##vso[task.setvariable variable=NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED]False" + else + echo "No internal endpoints found in NuGet.config" + fi +} + +# Workaround for https://github.com/microsoft/msbuild/issues/4430 +function InstallDotNetSdkAndRestoreArcade { + local dotnetTempDir="$repo_root/dotnet" + local dotnetSdkVersion="2.1.507" # After experimentation we know this version works when restoring the SDK (compared to 3.0.*) + local restoreProjPath="$repo_root/eng/common/restore.proj" + + echo "Installing dotnet SDK version $dotnetSdkVersion to restore Arcade SDK..." + echo "" > "$restoreProjPath" + + InstallDotNetSdk "$dotnetTempDir" "$dotnetSdkVersion" + + local res=`$dotnetTempDir/dotnet restore $restoreProjPath` + echo "Arcade SDK restored!" + + # Cleanup + if [ "$restoreProjPath" ]; then + rm "$restoreProjPath" + fi + + if [ "$dotnetTempDir" ]; then + rm -r $dotnetTempDir + fi +} + +source="${BASH_SOURCE[0]}" +operation='' +authToken='' +repoName='' + +while [[ $# > 0 ]]; do + opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + --operation) + operation=$2 + shift + ;; + --authtoken) + authToken=$2 + shift + ;; + *) + echo "Invalid argument: $1" + usage + exit 1 + ;; + esac + + shift +done + +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. "$scriptroot/tools.sh" + +if [ "$operation" = "setup" ]; then + SetupCredProvider $authToken +elif [ "$operation" = "install-restore" ]; then + InstallDotNetSdkAndRestoreArcade +else + echo "Unknown operation '$operation'!" +fi diff --git a/eng/common/internal/Directory.Build.props b/eng/common/internal/Directory.Build.props new file mode 100644 index 000000000..dbf99d82a --- /dev/null +++ b/eng/common/internal/Directory.Build.props @@ -0,0 +1,4 @@ + + + + diff --git a/eng/common/internal/Tools.csproj b/eng/common/internal/Tools.csproj new file mode 100644 index 000000000..f46d5efe2 --- /dev/null +++ b/eng/common/internal/Tools.csproj @@ -0,0 +1,28 @@ + + + + + net472 + false + false + + + + + + + + + + + https://devdiv.pkgs.visualstudio.com/_packaging/dotnet-core-internal-tooling/nuget/v3/index.json; + + + $(RestoreSources); + https://devdiv.pkgs.visualstudio.com/_packaging/VS/nuget/v3/index.json; + + + + + + diff --git a/eng/common/msbuild.ps1 b/eng/common/msbuild.ps1 new file mode 100644 index 000000000..eea19cd84 --- /dev/null +++ b/eng/common/msbuild.ps1 @@ -0,0 +1,27 @@ +[CmdletBinding(PositionalBinding=$false)] +Param( + [string] $verbosity = 'minimal', + [bool] $warnAsError = $true, + [bool] $nodeReuse = $true, + [switch] $ci, + [switch] $prepareMachine, + [switch] $excludePrereleaseVS, + [Parameter(ValueFromRemainingArguments=$true)][String[]]$extraArgs +) + +. $PSScriptRoot\tools.ps1 + +try { + if ($ci) { + $nodeReuse = $false + } + + MSBuild @extraArgs +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Build' -Message $_ + ExitWithExitCode 1 +} + +ExitWithExitCode 0 \ No newline at end of file diff --git a/eng/common/msbuild.sh b/eng/common/msbuild.sh new file mode 100755 index 000000000..20d3dad54 --- /dev/null +++ b/eng/common/msbuild.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" + +# resolve $source until the file is no longer a symlink +while [[ -h "$source" ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +verbosity='minimal' +warn_as_error=true +node_reuse=true +prepare_machine=false +extra_args='' + +while (($# > 0)); do + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" + case $lowerI in + --verbosity) + verbosity=$2 + shift 2 + ;; + --warnaserror) + warn_as_error=$2 + shift 2 + ;; + --nodereuse) + node_reuse=$2 + shift 2 + ;; + --ci) + ci=true + shift 1 + ;; + --preparemachine) + prepare_machine=true + shift 1 + ;; + *) + extra_args="$extra_args $1" + shift 1 + ;; + esac +done + +. "$scriptroot/tools.sh" + +if [[ "$ci" == true ]]; then + node_reuse=false +fi + +MSBuild $extra_args +ExitWithExitCode 0 diff --git a/eng/common/native/CommonLibrary.psm1 b/eng/common/native/CommonLibrary.psm1 new file mode 100644 index 000000000..adf707c8f --- /dev/null +++ b/eng/common/native/CommonLibrary.psm1 @@ -0,0 +1,399 @@ +<# +.SYNOPSIS +Helper module to install an archive to a directory + +.DESCRIPTION +Helper module to download and extract an archive to a specified directory + +.PARAMETER Uri +Uri of artifact to download + +.PARAMETER InstallDirectory +Directory to extract artifact contents to + +.PARAMETER Force +Force download / extraction if file or contents already exist. Default = False + +.PARAMETER DownloadRetries +Total number of retry attempts. Default = 5 + +.PARAMETER RetryWaitTimeInSeconds +Wait time between retry attempts in seconds. Default = 30 + +.NOTES +Returns False if download or extraction fail, True otherwise +#> +function DownloadAndExtract { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $Uri, + [Parameter(Mandatory=$True)] + [string] $InstallDirectory, + [switch] $Force = $False, + [int] $DownloadRetries = 5, + [int] $RetryWaitTimeInSeconds = 30 + ) + # Define verbose switch if undefined + $Verbose = $VerbosePreference -Eq "Continue" + + $TempToolPath = CommonLibrary\Get-TempPathFilename -Path $Uri + + # Download native tool + $DownloadStatus = CommonLibrary\Get-File -Uri $Uri ` + -Path $TempToolPath ` + -DownloadRetries $DownloadRetries ` + -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds ` + -Force:$Force ` + -Verbose:$Verbose + + if ($DownloadStatus -Eq $False) { + Write-Error "Download failed from $Uri" + return $False + } + + # Extract native tool + $UnzipStatus = CommonLibrary\Expand-Zip -ZipPath $TempToolPath ` + -OutputDirectory $InstallDirectory ` + -Force:$Force ` + -Verbose:$Verbose + + if ($UnzipStatus -Eq $False) { + # Retry Download one more time with Force=true + $DownloadRetryStatus = CommonLibrary\Get-File -Uri $Uri ` + -Path $TempToolPath ` + -DownloadRetries 1 ` + -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds ` + -Force:$True ` + -Verbose:$Verbose + + if ($DownloadRetryStatus -Eq $False) { + Write-Error "Last attempt of download failed as well" + return $False + } + + # Retry unzip again one more time with Force=true + $UnzipRetryStatus = CommonLibrary\Expand-Zip -ZipPath $TempToolPath ` + -OutputDirectory $InstallDirectory ` + -Force:$True ` + -Verbose:$Verbose + if ($UnzipRetryStatus -Eq $False) + { + Write-Error "Last attempt of unzip failed as well" + # Clean up partial zips and extracts + if (Test-Path $TempToolPath) { + Remove-Item $TempToolPath -Force + } + if (Test-Path $InstallDirectory) { + Remove-Item $InstallDirectory -Force -Recurse + } + return $False + } + } + + return $True +} + +<# +.SYNOPSIS +Download a file, retry on failure + +.DESCRIPTION +Download specified file and retry if attempt fails + +.PARAMETER Uri +Uri of file to download. If Uri is a local path, the file will be copied instead of downloaded + +.PARAMETER Path +Path to download or copy uri file to + +.PARAMETER Force +Overwrite existing file if present. Default = False + +.PARAMETER DownloadRetries +Total number of retry attempts. Default = 5 + +.PARAMETER RetryWaitTimeInSeconds +Wait time between retry attempts in seconds Default = 30 + +#> +function Get-File { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $Uri, + [Parameter(Mandatory=$True)] + [string] $Path, + [int] $DownloadRetries = 5, + [int] $RetryWaitTimeInSeconds = 30, + [switch] $Force = $False + ) + $Attempt = 0 + + if ($Force) { + if (Test-Path $Path) { + Remove-Item $Path -Force + } + } + if (Test-Path $Path) { + Write-Host "File '$Path' already exists, skipping download" + return $True + } + + $DownloadDirectory = Split-Path -ErrorAction Ignore -Path "$Path" -Parent + if (-Not (Test-Path $DownloadDirectory)) { + New-Item -path $DownloadDirectory -force -itemType "Directory" | Out-Null + } + + $TempPath = "$Path.tmp" + if (Test-Path -IsValid -Path $Uri) { + Write-Verbose "'$Uri' is a file path, copying temporarily to '$TempPath'" + Copy-Item -Path $Uri -Destination $TempPath + Write-Verbose "Moving temporary file to '$Path'" + Move-Item -Path $TempPath -Destination $Path + return $? + } + else { + Write-Verbose "Downloading $Uri" + # Don't display the console progress UI - it's a huge perf hit + $ProgressPreference = 'SilentlyContinue' + while($Attempt -Lt $DownloadRetries) + { + try { + Invoke-WebRequest -UseBasicParsing -Uri $Uri -OutFile $TempPath + Write-Verbose "Downloaded to temporary location '$TempPath'" + Move-Item -Path $TempPath -Destination $Path + Write-Verbose "Moved temporary file to '$Path'" + return $True + } + catch { + $Attempt++ + if ($Attempt -Lt $DownloadRetries) { + $AttemptsLeft = $DownloadRetries - $Attempt + Write-Warning "Download failed, $AttemptsLeft attempts remaining, will retry in $RetryWaitTimeInSeconds seconds" + Start-Sleep -Seconds $RetryWaitTimeInSeconds + } + else { + Write-Error $_ + Write-Error $_.Exception + } + } + } + } + + return $False +} + +<# +.SYNOPSIS +Generate a shim for a native tool + +.DESCRIPTION +Creates a wrapper script (shim) that passes arguments forward to native tool assembly + +.PARAMETER ShimName +The name of the shim + +.PARAMETER ShimDirectory +The directory where shims are stored + +.PARAMETER ToolFilePath +Path to file that shim forwards to + +.PARAMETER Force +Replace shim if already present. Default = False + +.NOTES +Returns $True if generating shim succeeds, $False otherwise +#> +function New-ScriptShim { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $ShimName, + [Parameter(Mandatory=$True)] + [string] $ShimDirectory, + [Parameter(Mandatory=$True)] + [string] $ToolFilePath, + [Parameter(Mandatory=$True)] + [string] $BaseUri, + [switch] $Force + ) + try { + Write-Verbose "Generating '$ShimName' shim" + + if (-Not (Test-Path $ToolFilePath)){ + Write-Error "Specified tool file path '$ToolFilePath' does not exist" + return $False + } + + # WinShimmer is a small .NET Framework program that creates .exe shims to bootstrapped programs + # Many of the checks for installed programs expect a .exe extension for Windows tools, rather + # than a .bat or .cmd file. + # Source: https://github.com/dotnet/arcade/tree/master/src/WinShimmer + if (-Not (Test-Path "$ShimDirectory\WinShimmer\winshimmer.exe")) { + $InstallStatus = DownloadAndExtract -Uri "$BaseUri/windows/winshimmer/WinShimmer.zip" ` + -InstallDirectory $ShimDirectory\WinShimmer ` + -Force:$Force ` + -DownloadRetries 2 ` + -RetryWaitTimeInSeconds 5 ` + -Verbose:$Verbose + } + + if ((Test-Path (Join-Path $ShimDirectory "$ShimName.exe"))) { + Write-Host "$ShimName.exe already exists; replacing..." + Remove-Item (Join-Path $ShimDirectory "$ShimName.exe") + } + + & "$ShimDirectory\WinShimmer\winshimmer.exe" $ShimName $ToolFilePath $ShimDirectory + return $True + } + catch { + Write-Host $_ + Write-Host $_.Exception + return $False + } +} + +<# +.SYNOPSIS +Returns the machine architecture of the host machine + +.NOTES +Returns 'x64' on 64 bit machines + Returns 'x86' on 32 bit machines +#> +function Get-MachineArchitecture { + $ProcessorArchitecture = $Env:PROCESSOR_ARCHITECTURE + $ProcessorArchitectureW6432 = $Env:PROCESSOR_ARCHITEW6432 + if($ProcessorArchitecture -Eq "X86") + { + if(($ProcessorArchitectureW6432 -Eq "") -Or + ($ProcessorArchitectureW6432 -Eq "X86")) { + return "x86" + } + $ProcessorArchitecture = $ProcessorArchitectureW6432 + } + if (($ProcessorArchitecture -Eq "AMD64") -Or + ($ProcessorArchitecture -Eq "IA64") -Or + ($ProcessorArchitecture -Eq "ARM64")) { + return "x64" + } + return "x86" +} + +<# +.SYNOPSIS +Get the name of a temporary folder under the native install directory +#> +function Get-TempDirectory { + return Join-Path (Get-NativeInstallDirectory) "temp/" +} + +function Get-TempPathFilename { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $Path + ) + $TempDir = CommonLibrary\Get-TempDirectory + $TempFilename = Split-Path $Path -leaf + $TempPath = Join-Path $TempDir $TempFilename + return $TempPath +} + +<# +.SYNOPSIS +Returns the base directory to use for native tool installation + +.NOTES +Returns the value of the NETCOREENG_INSTALL_DIRECTORY if that environment variable +is set, or otherwise returns an install directory under the %USERPROFILE% +#> +function Get-NativeInstallDirectory { + $InstallDir = $Env:NETCOREENG_INSTALL_DIRECTORY + if (!$InstallDir) { + $InstallDir = Join-Path $Env:USERPROFILE ".netcoreeng/native/" + } + return $InstallDir +} + +<# +.SYNOPSIS +Unzip an archive + +.DESCRIPTION +Powershell module to unzip an archive to a specified directory + +.PARAMETER ZipPath (Required) +Path to archive to unzip + +.PARAMETER OutputDirectory (Required) +Output directory for archive contents + +.PARAMETER Force +Overwrite output directory contents if they already exist + +.NOTES +- Returns True and does not perform an extraction if output directory already exists but Overwrite is not True. +- Returns True if unzip operation is successful +- Returns False if Overwrite is True and it is unable to remove contents of OutputDirectory +- Returns False if unable to extract zip archive +#> +function Expand-Zip { + [CmdletBinding(PositionalBinding=$false)] + Param ( + [Parameter(Mandatory=$True)] + [string] $ZipPath, + [Parameter(Mandatory=$True)] + [string] $OutputDirectory, + [switch] $Force + ) + + Write-Verbose "Extracting '$ZipPath' to '$OutputDirectory'" + try { + if ((Test-Path $OutputDirectory) -And (-Not $Force)) { + Write-Host "Directory '$OutputDirectory' already exists, skipping extract" + return $True + } + if (Test-Path $OutputDirectory) { + Write-Verbose "'Force' is 'True', but '$OutputDirectory' exists, removing directory" + Remove-Item $OutputDirectory -Force -Recurse + if ($? -Eq $False) { + Write-Error "Unable to remove '$OutputDirectory'" + return $False + } + } + + $TempOutputDirectory = Join-Path "$(Split-Path -Parent $OutputDirectory)" "$(Split-Path -Leaf $OutputDirectory).tmp" + if (Test-Path $TempOutputDirectory) { + Remove-Item $TempOutputDirectory -Force -Recurse + } + New-Item -Path $TempOutputDirectory -Force -ItemType "Directory" | Out-Null + + Add-Type -assembly "system.io.compression.filesystem" + [io.compression.zipfile]::ExtractToDirectory("$ZipPath", "$TempOutputDirectory") + if ($? -Eq $False) { + Write-Error "Unable to extract '$ZipPath'" + return $False + } + + Move-Item -Path $TempOutputDirectory -Destination $OutputDirectory + } + catch { + Write-Host $_ + Write-Host $_.Exception + + return $False + } + return $True +} + +export-modulemember -function DownloadAndExtract +export-modulemember -function Expand-Zip +export-modulemember -function Get-File +export-modulemember -function Get-MachineArchitecture +export-modulemember -function Get-NativeInstallDirectory +export-modulemember -function Get-TempDirectory +export-modulemember -function Get-TempPathFilename +export-modulemember -function New-ScriptShim diff --git a/eng/common/native/common-library.sh b/eng/common/native/common-library.sh new file mode 100755 index 000000000..bf272dcf5 --- /dev/null +++ b/eng/common/native/common-library.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash + +function GetNativeInstallDirectory { + local install_dir + + if [[ -z $NETCOREENG_INSTALL_DIRECTORY ]]; then + install_dir=$HOME/.netcoreeng/native/ + else + install_dir=$NETCOREENG_INSTALL_DIRECTORY + fi + + echo $install_dir + return 0 +} + +function GetTempDirectory { + + echo $(GetNativeInstallDirectory)temp/ + return 0 +} + +function ExpandZip { + local zip_path=$1 + local output_directory=$2 + local force=${3:-false} + + echo "Extracting $zip_path to $output_directory" + if [[ -d $output_directory ]] && [[ $force = false ]]; then + echo "Directory '$output_directory' already exists, skipping extract" + return 0 + fi + + if [[ -d $output_directory ]]; then + echo "'Force flag enabled, but '$output_directory' exists. Removing directory" + rm -rf $output_directory + if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Unable to remove '$output_directory'" + return 1 + fi + fi + + echo "Creating directory: '$output_directory'" + mkdir -p $output_directory + + echo "Extracting archive" + tar -xf $zip_path -C $output_directory + if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Unable to extract '$zip_path'" + return 1 + fi + + return 0 +} + +function GetCurrentOS { + local unameOut="$(uname -s)" + case $unameOut in + Linux*) echo "Linux";; + Darwin*) echo "MacOS";; + esac + return 0 +} + +function GetFile { + local uri=$1 + local path=$2 + local force=${3:-false} + local download_retries=${4:-5} + local retry_wait_time_seconds=${5:-30} + + if [[ -f $path ]]; then + if [[ $force = false ]]; then + echo "File '$path' already exists. Skipping download" + return 0 + else + rm -rf $path + fi + fi + + if [[ -f $uri ]]; then + echo "'$uri' is a file path, copying file to '$path'" + cp $uri $path + return $? + fi + + echo "Downloading $uri" + # Use curl if available, otherwise use wget + if command -v curl > /dev/null; then + curl "$uri" -sSL --retry $download_retries --retry-delay $retry_wait_time_seconds --create-dirs -o "$path" --fail + else + wget -q -O "$path" "$uri" --tries="$download_retries" + fi + + return $? +} + +function GetTempPathFileName { + local path=$1 + + local temp_dir=$(GetTempDirectory) + local temp_file_name=$(basename $path) + echo $temp_dir$temp_file_name + return 0 +} + +function DownloadAndExtract { + local uri=$1 + local installDir=$2 + local force=${3:-false} + local download_retries=${4:-5} + local retry_wait_time_seconds=${5:-30} + + local temp_tool_path=$(GetTempPathFileName $uri) + + echo "downloading to: $temp_tool_path" + + # Download file + GetFile "$uri" "$temp_tool_path" $force $download_retries $retry_wait_time_seconds + if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Failed to download '$uri' to '$temp_tool_path'." + return 1 + fi + + # Extract File + echo "extracting from $temp_tool_path to $installDir" + ExpandZip "$temp_tool_path" "$installDir" $force $download_retries $retry_wait_time_seconds + if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Failed to extract '$temp_tool_path' to '$installDir'." + return 1 + fi + + return 0 +} + +function NewScriptShim { + local shimpath=$1 + local tool_file_path=$2 + local force=${3:-false} + + echo "Generating '$shimpath' shim" + if [[ -f $shimpath ]]; then + if [[ $force = false ]]; then + echo "File '$shimpath' already exists." >&2 + return 1 + else + rm -rf $shimpath + fi + fi + + if [[ ! -f $tool_file_path ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Specified tool file path:'$tool_file_path' does not exist" + return 1 + fi + + local shim_contents=$'#!/usr/bin/env bash\n' + shim_contents+="SHIMARGS="$'$1\n' + shim_contents+="$tool_file_path"$' $SHIMARGS\n' + + # Write shim file + echo "$shim_contents" > $shimpath + + chmod +x $shimpath + + echo "Finished generating shim '$shimpath'" + + return $? +} + diff --git a/eng/common/native/find-native-compiler.sh b/eng/common/native/find-native-compiler.sh new file mode 100644 index 000000000..aed19d07d --- /dev/null +++ b/eng/common/native/find-native-compiler.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# +# This file locates the native compiler with the given name and version and sets the environment variables to locate it. +# + +source="${BASH_SOURCE[0]}" + +# resolve $SOURCE until the file is no longer a symlink +while [[ -h $source ]]; do + scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + source="$(readlink "$source")" + + # if $source was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $source != /* ]] && source="$scriptroot/$source" +done +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +if [ $# -lt 0 ] +then + echo "Usage..." + echo "find-native-compiler.sh " + echo "Specify the name of compiler (clang or gcc)." + echo "Specify the major version of compiler." + echo "Specify the minor version of compiler." + exit 1 +fi + +. $scriptroot/../pipeline-logging-functions.sh + +compiler="$1" +cxxCompiler="$compiler++" +majorVersion="$2" +minorVersion="$3" + +if [ "$compiler" = "gcc" ]; then cxxCompiler="g++"; fi + +check_version_exists() { + desired_version=-1 + + # Set up the environment to be used for building with the desired compiler. + if command -v "$compiler-$1.$2" > /dev/null; then + desired_version="-$1.$2" + elif command -v "$compiler$1$2" > /dev/null; then + desired_version="$1$2" + elif command -v "$compiler-$1$2" > /dev/null; then + desired_version="-$1$2" + fi + + echo "$desired_version" +} + +if [ -z "$CLR_CC" ]; then + + # Set default versions + if [ -z "$majorVersion" ]; then + # note: gcc (all versions) and clang versions higher than 6 do not have minor version in file name, if it is zero. + if [ "$compiler" = "clang" ]; then versions=( 9 8 7 6.0 5.0 4.0 3.9 3.8 3.7 3.6 3.5 ) + elif [ "$compiler" = "gcc" ]; then versions=( 9 8 7 6 5 4.9 ); fi + + for version in "${versions[@]}"; do + parts=(${version//./ }) + desired_version="$(check_version_exists "${parts[0]}" "${parts[1]}")" + if [ "$desired_version" != "-1" ]; then majorVersion="${parts[0]}"; break; fi + done + + if [ -z "$majorVersion" ]; then + if command -v "$compiler" > /dev/null; then + if [ "$(uname)" != "Darwin" ]; then + Write-PipelineTelemetryError -category "Build" -type "warning" "Specific version of $compiler not found, falling back to use the one in PATH." + fi + export CC="$(command -v "$compiler")" + export CXX="$(command -v "$cxxCompiler")" + else + Write-PipelineTelemetryError -category "Build" "No usable version of $compiler found." + exit 1 + fi + else + if [ "$compiler" = "clang" ] && [ "$majorVersion" -lt 5 ]; then + if [ "$build_arch" = "arm" ] || [ "$build_arch" = "armel" ]; then + if command -v "$compiler" > /dev/null; then + Write-PipelineTelemetryError -category "Build" -type "warning" "Found clang version $majorVersion which is not supported on arm/armel architectures, falling back to use clang from PATH." + export CC="$(command -v "$compiler")" + export CXX="$(command -v "$cxxCompiler")" + else + Write-PipelineTelemetryError -category "Build" "Found clang version $majorVersion which is not supported on arm/armel architectures, and there is no clang in PATH." + exit 1 + fi + fi + fi + fi + else + desired_version="$(check_version_exists "$majorVersion" "$minorVersion")" + if [ "$desired_version" = "-1" ]; then + Write-PipelineTelemetryError -category "Build" "Could not find specific version of $compiler: $majorVersion $minorVersion." + exit 1 + fi + fi + + if [ -z "$CC" ]; then + export CC="$(command -v "$compiler$desired_version")" + export CXX="$(command -v "$cxxCompiler$desired_version")" + if [ -z "$CXX" ]; then export CXX="$(command -v "$cxxCompiler")"; fi + fi +else + if [ ! -f "$CLR_CC" ]; then + Write-PipelineTelemetryError -category "Build" "CLR_CC is set but path '$CLR_CC' does not exist" + exit 1 + fi + export CC="$CLR_CC" + export CXX="$CLR_CXX" +fi + +if [ -z "$CC" ]; then + Write-PipelineTelemetryError -category "Build" "Unable to find $compiler." + exit 1 +fi + +export CCC_CC="$CC" +export CCC_CXX="$CXX" +export SCAN_BUILD_COMMAND="$(command -v "scan-build$desired_version")" diff --git a/eng/common/native/install-cmake-test.sh b/eng/common/native/install-cmake-test.sh new file mode 100755 index 000000000..8a5e7cf0d --- /dev/null +++ b/eng/common/native/install-cmake-test.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. $scriptroot/common-library.sh + +base_uri= +install_path= +version= +clean=false +force=false +download_retries=5 +retry_wait_time_seconds=30 + +while (($# > 0)); do + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" + case $lowerI in + --baseuri) + base_uri=$2 + shift 2 + ;; + --installpath) + install_path=$2 + shift 2 + ;; + --version) + version=$2 + shift 2 + ;; + --clean) + clean=true + shift 1 + ;; + --force) + force=true + shift 1 + ;; + --downloadretries) + download_retries=$2 + shift 2 + ;; + --retrywaittimeseconds) + retry_wait_time_seconds=$2 + shift 2 + ;; + --help) + echo "Common settings:" + echo " --baseuri Base file directory or Url wrom which to acquire tool archives" + echo " --installpath Base directory to install native tool to" + echo " --clean Don't install the tool, just clean up the current install of the tool" + echo " --force Force install of tools even if they previously exist" + echo " --help Print help and exit" + echo "" + echo "Advanced settings:" + echo " --downloadretries Total number of retry attempts" + echo " --retrywaittimeseconds Wait time between retry attempts in seconds" + echo "" + exit 0 + ;; + esac +done + +tool_name="cmake-test" +tool_os=$(GetCurrentOS) +tool_folder="$(echo $tool_os | tr "[:upper:]" "[:lower:]")" +tool_arch="x86_64" +tool_name_moniker="$tool_name-$version-$tool_os-$tool_arch" +tool_install_directory="$install_path/$tool_name/$version" +tool_file_path="$tool_install_directory/$tool_name_moniker/bin/$tool_name" +shim_path="$install_path/$tool_name.sh" +uri="${base_uri}/$tool_folder/$tool_name/$tool_name_moniker.tar.gz" + +# Clean up tool and installers +if [[ $clean = true ]]; then + echo "Cleaning $tool_install_directory" + if [[ -d $tool_install_directory ]]; then + rm -rf $tool_install_directory + fi + + echo "Cleaning $shim_path" + if [[ -f $shim_path ]]; then + rm -rf $shim_path + fi + + tool_temp_path=$(GetTempPathFileName $uri) + echo "Cleaning $tool_temp_path" + if [[ -f $tool_temp_path ]]; then + rm -rf $tool_temp_path + fi + + exit 0 +fi + +# Install tool +if [[ -f $tool_file_path ]] && [[ $force = false ]]; then + echo "$tool_name ($version) already exists, skipping install" + exit 0 +fi + +DownloadAndExtract $uri $tool_install_directory $force $download_retries $retry_wait_time_seconds + +if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Installation failed' + exit 1 +fi + +# Generate Shim +# Always rewrite shims so that we are referencing the expected version +NewScriptShim $shim_path $tool_file_path true + +if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Shim generation failed' + exit 1 +fi + +exit 0 diff --git a/eng/common/native/install-cmake.sh b/eng/common/native/install-cmake.sh new file mode 100755 index 000000000..de496beeb --- /dev/null +++ b/eng/common/native/install-cmake.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash + +source="${BASH_SOURCE[0]}" +scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" + +. $scriptroot/common-library.sh + +base_uri= +install_path= +version= +clean=false +force=false +download_retries=5 +retry_wait_time_seconds=30 + +while (($# > 0)); do + lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" + case $lowerI in + --baseuri) + base_uri=$2 + shift 2 + ;; + --installpath) + install_path=$2 + shift 2 + ;; + --version) + version=$2 + shift 2 + ;; + --clean) + clean=true + shift 1 + ;; + --force) + force=true + shift 1 + ;; + --downloadretries) + download_retries=$2 + shift 2 + ;; + --retrywaittimeseconds) + retry_wait_time_seconds=$2 + shift 2 + ;; + --help) + echo "Common settings:" + echo " --baseuri Base file directory or Url wrom which to acquire tool archives" + echo " --installpath Base directory to install native tool to" + echo " --clean Don't install the tool, just clean up the current install of the tool" + echo " --force Force install of tools even if they previously exist" + echo " --help Print help and exit" + echo "" + echo "Advanced settings:" + echo " --downloadretries Total number of retry attempts" + echo " --retrywaittimeseconds Wait time between retry attempts in seconds" + echo "" + exit 0 + ;; + esac +done + +tool_name="cmake" +tool_os=$(GetCurrentOS) +tool_folder="$(echo $tool_os | tr "[:upper:]" "[:lower:]")" +tool_arch="x86_64" +tool_name_moniker="$tool_name-$version-$tool_os-$tool_arch" +tool_install_directory="$install_path/$tool_name/$version" +tool_file_path="$tool_install_directory/$tool_name_moniker/bin/$tool_name" +shim_path="$install_path/$tool_name.sh" +uri="${base_uri}/$tool_folder/$tool_name/$tool_name_moniker.tar.gz" + +# Clean up tool and installers +if [[ $clean = true ]]; then + echo "Cleaning $tool_install_directory" + if [[ -d $tool_install_directory ]]; then + rm -rf $tool_install_directory + fi + + echo "Cleaning $shim_path" + if [[ -f $shim_path ]]; then + rm -rf $shim_path + fi + + tool_temp_path=$(GetTempPathFileName $uri) + echo "Cleaning $tool_temp_path" + if [[ -f $tool_temp_path ]]; then + rm -rf $tool_temp_path + fi + + exit 0 +fi + +# Install tool +if [[ -f $tool_file_path ]] && [[ $force = false ]]; then + echo "$tool_name ($version) already exists, skipping install" + exit 0 +fi + +DownloadAndExtract $uri $tool_install_directory $force $download_retries $retry_wait_time_seconds + +if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Installation failed' + exit 1 +fi + +# Generate Shim +# Always rewrite shims so that we are referencing the expected version +NewScriptShim $shim_path $tool_file_path true + +if [[ $? != 0 ]]; then + Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Shim generation failed' + exit 1 +fi + +exit 0 diff --git a/eng/common/native/install-tool.ps1 b/eng/common/native/install-tool.ps1 new file mode 100644 index 000000000..78f2d84a4 --- /dev/null +++ b/eng/common/native/install-tool.ps1 @@ -0,0 +1,132 @@ +<# +.SYNOPSIS +Install native tool + +.DESCRIPTION +Install cmake native tool from Azure blob storage + +.PARAMETER InstallPath +Base directory to install native tool to + +.PARAMETER BaseUri +Base file directory or Url from which to acquire tool archives + +.PARAMETER CommonLibraryDirectory +Path to folder containing common library modules + +.PARAMETER Force +Force install of tools even if they previously exist + +.PARAMETER Clean +Don't install the tool, just clean up the current install of the tool + +.PARAMETER DownloadRetries +Total number of retry attempts + +.PARAMETER RetryWaitTimeInSeconds +Wait time between retry attempts in seconds + +.NOTES +Returns 0 if install succeeds, 1 otherwise +#> +[CmdletBinding(PositionalBinding=$false)] +Param ( + [Parameter(Mandatory=$True)] + [string] $ToolName, + [Parameter(Mandatory=$True)] + [string] $InstallPath, + [Parameter(Mandatory=$True)] + [string] $BaseUri, + [Parameter(Mandatory=$True)] + [string] $Version, + [string] $CommonLibraryDirectory = $PSScriptRoot, + [switch] $Force = $False, + [switch] $Clean = $False, + [int] $DownloadRetries = 5, + [int] $RetryWaitTimeInSeconds = 30 +) + +. $PSScriptRoot\..\pipeline-logging-functions.ps1 + +# Import common library modules +Import-Module -Name (Join-Path $CommonLibraryDirectory "CommonLibrary.psm1") + +try { + # Define verbose switch if undefined + $Verbose = $VerbosePreference -Eq "Continue" + + $Arch = CommonLibrary\Get-MachineArchitecture + $ToolOs = "win64" + if($Arch -Eq "x32") { + $ToolOs = "win32" + } + $ToolNameMoniker = "$ToolName-$Version-$ToolOs-$Arch" + $ToolInstallDirectory = Join-Path $InstallPath "$ToolName\$Version\" + $Uri = "$BaseUri/windows/$ToolName/$ToolNameMoniker.zip" + $ShimPath = Join-Path $InstallPath "$ToolName.exe" + + if ($Clean) { + Write-Host "Cleaning $ToolInstallDirectory" + if (Test-Path $ToolInstallDirectory) { + Remove-Item $ToolInstallDirectory -Force -Recurse + } + Write-Host "Cleaning $ShimPath" + if (Test-Path $ShimPath) { + Remove-Item $ShimPath -Force + } + $ToolTempPath = CommonLibrary\Get-TempPathFilename -Path $Uri + Write-Host "Cleaning $ToolTempPath" + if (Test-Path $ToolTempPath) { + Remove-Item $ToolTempPath -Force + } + exit 0 + } + + # Install tool + if ((Test-Path $ToolInstallDirectory) -And (-Not $Force)) { + Write-Verbose "$ToolName ($Version) already exists, skipping install" + } + else { + $InstallStatus = CommonLibrary\DownloadAndExtract -Uri $Uri ` + -InstallDirectory $ToolInstallDirectory ` + -Force:$Force ` + -DownloadRetries $DownloadRetries ` + -RetryWaitTimeInSeconds $RetryWaitTimeInSeconds ` + -Verbose:$Verbose + + if ($InstallStatus -Eq $False) { + Write-PipelineTelemetryError "Installation failed" -Category "NativeToolsetBootstrapping" + exit 1 + } + } + + $ToolFilePath = Get-ChildItem $ToolInstallDirectory -Recurse -Filter "$ToolName.exe" | % { $_.FullName } + if (@($ToolFilePath).Length -Gt 1) { + Write-Error "There are multiple copies of $ToolName in $($ToolInstallDirectory): `n$(@($ToolFilePath | out-string))" + exit 1 + } elseif (@($ToolFilePath).Length -Lt 1) { + Write-Host "$ToolName was not found in $ToolInstallDirectory." + exit 1 + } + + # Generate shim + # Always rewrite shims so that we are referencing the expected version + $GenerateShimStatus = CommonLibrary\New-ScriptShim -ShimName $ToolName ` + -ShimDirectory $InstallPath ` + -ToolFilePath "$ToolFilePath" ` + -BaseUri $BaseUri ` + -Force:$Force ` + -Verbose:$Verbose + + if ($GenerateShimStatus -Eq $False) { + Write-PipelineTelemetryError "Generate shim failed" -Category "NativeToolsetBootstrapping" + return 1 + } + + exit 0 +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category "NativeToolsetBootstrapping" -Message $_ + exit 1 +} diff --git a/eng/common/pipeline-logging-functions.ps1 b/eng/common/pipeline-logging-functions.ps1 new file mode 100644 index 000000000..8e422c561 --- /dev/null +++ b/eng/common/pipeline-logging-functions.ps1 @@ -0,0 +1,260 @@ +# Source for this file was taken from https://github.com/microsoft/azure-pipelines-task-lib/blob/11c9439d4af17e6475d9fe058e6b2e03914d17e6/powershell/VstsTaskSdk/LoggingCommandFunctions.ps1 and modified. + +# NOTE: You should not be calling these method directly as they are likely to change. Instead you should be calling the Write-Pipeline* functions defined in tools.ps1 + +$script:loggingCommandPrefix = '##vso[' +$script:loggingCommandEscapeMappings = @( # TODO: WHAT ABOUT "="? WHAT ABOUT "%"? + New-Object psobject -Property @{ Token = ';' ; Replacement = '%3B' } + New-Object psobject -Property @{ Token = "`r" ; Replacement = '%0D' } + New-Object psobject -Property @{ Token = "`n" ; Replacement = '%0A' } + New-Object psobject -Property @{ Token = "]" ; Replacement = '%5D' } +) +# TODO: BUG: Escape % ??? +# TODO: Add test to verify don't need to escape "=". + +# Specify "-Force" to force pipeline formatted output even if "$ci" is false or not set +function Write-PipelineTelemetryError { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Category, + [Parameter(Mandatory = $true)] + [string]$Message, + [Parameter(Mandatory = $false)] + [string]$Type = 'error', + [string]$ErrCode, + [string]$SourcePath, + [string]$LineNumber, + [string]$ColumnNumber, + [switch]$AsOutput, + [switch]$Force) + + $PSBoundParameters.Remove('Category') | Out-Null + + if ($Force -Or ((Test-Path variable:ci) -And $ci)) { + $Message = "(NETCORE_ENGINEERING_TELEMETRY=$Category) $Message" + } + $PSBoundParameters.Remove('Message') | Out-Null + $PSBoundParameters.Add('Message', $Message) + Write-PipelineTaskError @PSBoundParameters +} + +# Specify "-Force" to force pipeline formatted output even if "$ci" is false or not set +function Write-PipelineTaskError { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Message, + [Parameter(Mandatory = $false)] + [string]$Type = 'error', + [string]$ErrCode, + [string]$SourcePath, + [string]$LineNumber, + [string]$ColumnNumber, + [switch]$AsOutput, + [switch]$Force + ) + + if (!$Force -And (-Not (Test-Path variable:ci) -Or !$ci)) { + if ($Type -eq 'error') { + Write-Host $Message -ForegroundColor Red + return + } + elseif ($Type -eq 'warning') { + Write-Host $Message -ForegroundColor Yellow + return + } + } + + if (($Type -ne 'error') -and ($Type -ne 'warning')) { + Write-Host $Message + return + } + $PSBoundParameters.Remove('Force') | Out-Null + if (-not $PSBoundParameters.ContainsKey('Type')) { + $PSBoundParameters.Add('Type', 'error') + } + Write-LogIssue @PSBoundParameters +} + +function Write-PipelineSetVariable { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Name, + [string]$Value, + [switch]$Secret, + [switch]$AsOutput, + [bool]$IsMultiJobVariable = $true) + + if ((Test-Path variable:ci) -And $ci) { + Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data $Value -Properties @{ + 'variable' = $Name + 'isSecret' = $Secret + 'isOutput' = $IsMultiJobVariable + } -AsOutput:$AsOutput + } +} + +function Write-PipelinePrependPath { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Path, + [switch]$AsOutput) + + if ((Test-Path variable:ci) -And $ci) { + Write-LoggingCommand -Area 'task' -Event 'prependpath' -Data $Path -AsOutput:$AsOutput + } +} + +function Write-PipelineSetResult { + [CmdletBinding()] + param( + [ValidateSet("Succeeded", "SucceededWithIssues", "Failed", "Cancelled", "Skipped")] + [Parameter(Mandatory = $true)] + [string]$Result, + [string]$Message) + if ((Test-Path variable:ci) -And $ci) { + Write-LoggingCommand -Area 'task' -Event 'complete' -Data $Message -Properties @{ + 'result' = $Result + } + } +} + +<######################################## +# Private functions. +########################################> +function Format-LoggingCommandData { + [CmdletBinding()] + param([string]$Value, [switch]$Reverse) + + if (!$Value) { + return '' + } + + if (!$Reverse) { + foreach ($mapping in $script:loggingCommandEscapeMappings) { + $Value = $Value.Replace($mapping.Token, $mapping.Replacement) + } + } + else { + for ($i = $script:loggingCommandEscapeMappings.Length - 1 ; $i -ge 0 ; $i--) { + $mapping = $script:loggingCommandEscapeMappings[$i] + $Value = $Value.Replace($mapping.Replacement, $mapping.Token) + } + } + + return $Value +} + +function Format-LoggingCommand { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$Area, + [Parameter(Mandatory = $true)] + [string]$Event, + [string]$Data, + [hashtable]$Properties) + + # Append the preamble. + [System.Text.StringBuilder]$sb = New-Object -TypeName System.Text.StringBuilder + $null = $sb.Append($script:loggingCommandPrefix).Append($Area).Append('.').Append($Event) + + # Append the properties. + if ($Properties) { + $first = $true + foreach ($key in $Properties.Keys) { + [string]$value = Format-LoggingCommandData $Properties[$key] + if ($value) { + if ($first) { + $null = $sb.Append(' ') + $first = $false + } + else { + $null = $sb.Append(';') + } + + $null = $sb.Append("$key=$value") + } + } + } + + # Append the tail and output the value. + $Data = Format-LoggingCommandData $Data + $sb.Append(']').Append($Data).ToString() +} + +function Write-LoggingCommand { + [CmdletBinding(DefaultParameterSetName = 'Parameters')] + param( + [Parameter(Mandatory = $true, ParameterSetName = 'Parameters')] + [string]$Area, + [Parameter(Mandatory = $true, ParameterSetName = 'Parameters')] + [string]$Event, + [Parameter(ParameterSetName = 'Parameters')] + [string]$Data, + [Parameter(ParameterSetName = 'Parameters')] + [hashtable]$Properties, + [Parameter(Mandatory = $true, ParameterSetName = 'Object')] + $Command, + [switch]$AsOutput) + + if ($PSCmdlet.ParameterSetName -eq 'Object') { + Write-LoggingCommand -Area $Command.Area -Event $Command.Event -Data $Command.Data -Properties $Command.Properties -AsOutput:$AsOutput + return + } + + $command = Format-LoggingCommand -Area $Area -Event $Event -Data $Data -Properties $Properties + if ($AsOutput) { + $command + } + else { + Write-Host $command + } +} + +function Write-LogIssue { + [CmdletBinding()] + param( + [ValidateSet('warning', 'error')] + [Parameter(Mandatory = $true)] + [string]$Type, + [string]$Message, + [string]$ErrCode, + [string]$SourcePath, + [string]$LineNumber, + [string]$ColumnNumber, + [switch]$AsOutput) + + $command = Format-LoggingCommand -Area 'task' -Event 'logissue' -Data $Message -Properties @{ + 'type' = $Type + 'code' = $ErrCode + 'sourcepath' = $SourcePath + 'linenumber' = $LineNumber + 'columnnumber' = $ColumnNumber + } + if ($AsOutput) { + return $command + } + + if ($Type -eq 'error') { + $foregroundColor = $host.PrivateData.ErrorForegroundColor + $backgroundColor = $host.PrivateData.ErrorBackgroundColor + if ($foregroundColor -isnot [System.ConsoleColor] -or $backgroundColor -isnot [System.ConsoleColor]) { + $foregroundColor = [System.ConsoleColor]::Red + $backgroundColor = [System.ConsoleColor]::Black + } + } + else { + $foregroundColor = $host.PrivateData.WarningForegroundColor + $backgroundColor = $host.PrivateData.WarningBackgroundColor + if ($foregroundColor -isnot [System.ConsoleColor] -or $backgroundColor -isnot [System.ConsoleColor]) { + $foregroundColor = [System.ConsoleColor]::Yellow + $backgroundColor = [System.ConsoleColor]::Black + } + } + + Write-Host $command -ForegroundColor $foregroundColor -BackgroundColor $backgroundColor +} diff --git a/eng/common/pipeline-logging-functions.sh b/eng/common/pipeline-logging-functions.sh new file mode 100755 index 000000000..6a0b2255e --- /dev/null +++ b/eng/common/pipeline-logging-functions.sh @@ -0,0 +1,206 @@ +#!/usr/bin/env bash + +function Write-PipelineTelemetryError { + local telemetry_category='' + local force=false + local function_args=() + local message='' + while [[ $# -gt 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -category|-c) + telemetry_category=$2 + shift + ;; + -force|-f) + force=true + ;; + -*) + function_args+=("$1 $2") + shift + ;; + *) + message=$* + ;; + esac + shift + done + + if [[ $force != true ]] && [[ "$ci" != true ]]; then + echo "$message" >&2 + return + fi + + if [[ $force == true ]]; then + function_args+=("-force") + fi + message="(NETCORE_ENGINEERING_TELEMETRY=$telemetry_category) $message" + function_args+=("$message") + Write-PipelineTaskError ${function_args[@]} +} + +function Write-PipelineTaskError { + local message_type="error" + local sourcepath='' + local linenumber='' + local columnnumber='' + local error_code='' + local force=false + + while [[ $# -gt 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -type|-t) + message_type=$2 + shift + ;; + -sourcepath|-s) + sourcepath=$2 + shift + ;; + -linenumber|-ln) + linenumber=$2 + shift + ;; + -columnnumber|-cn) + columnnumber=$2 + shift + ;; + -errcode|-e) + error_code=$2 + shift + ;; + -force|-f) + force=true + ;; + *) + break + ;; + esac + + shift + done + + if [[ $force != true ]] && [[ "$ci" != true ]]; then + echo "$@" >&2 + return + fi + + local message="##vso[task.logissue" + + message="$message type=$message_type" + + if [ -n "$sourcepath" ]; then + message="$message;sourcepath=$sourcepath" + fi + + if [ -n "$linenumber" ]; then + message="$message;linenumber=$linenumber" + fi + + if [ -n "$columnnumber" ]; then + message="$message;columnnumber=$columnnumber" + fi + + if [ -n "$error_code" ]; then + message="$message;code=$error_code" + fi + + message="$message]$*" + echo "$message" +} + +function Write-PipelineSetVariable { + if [[ "$ci" != true ]]; then + return + fi + + local name='' + local value='' + local secret=false + local as_output=false + local is_multi_job_variable=true + + while [[ $# -gt 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -name|-n) + name=$2 + shift + ;; + -value|-v) + value=$2 + shift + ;; + -secret|-s) + secret=true + ;; + -as_output|-a) + as_output=true + ;; + -is_multi_job_variable|-i) + is_multi_job_variable=$2 + shift + ;; + esac + shift + done + + value=${value/;/%3B} + value=${value/\\r/%0D} + value=${value/\\n/%0A} + value=${value/]/%5D} + + local message="##vso[task.setvariable variable=$name;isSecret=$secret;isOutput=$is_multi_job_variable]$value" + + if [[ "$as_output" == true ]]; then + $message + else + echo "$message" + fi +} + +function Write-PipelinePrependPath { + local prepend_path='' + + while [[ $# -gt 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -path|-p) + prepend_path=$2 + shift + ;; + esac + shift + done + + export PATH="$prepend_path:$PATH" + + if [[ "$ci" == true ]]; then + echo "##vso[task.prependpath]$prepend_path" + fi +} + +function Write-PipelineSetResult { + local result='' + local message='' + + while [[ $# -gt 0 ]]; do + opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")" + case "$opt" in + -result|-r) + result=$2 + shift + ;; + -message|-m) + message=$2 + shift + ;; + esac + shift + done + + if [[ "$ci" == true ]]; then + echo "##vso[task.complete result=$result;]$message" + fi +} diff --git a/eng/common/post-build/add-build-to-channel.ps1 b/eng/common/post-build/add-build-to-channel.ps1 new file mode 100644 index 000000000..de2d95792 --- /dev/null +++ b/eng/common/post-build/add-build-to-channel.ps1 @@ -0,0 +1,48 @@ +param( + [Parameter(Mandatory=$true)][int] $BuildId, + [Parameter(Mandatory=$true)][int] $ChannelId, + [Parameter(Mandatory=$true)][string] $MaestroApiAccessToken, + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro-prod.westus2.cloudapp.azure.com', + [Parameter(Mandatory=$false)][string] $MaestroApiVersion = '2019-01-16' +) + +try { + . $PSScriptRoot\post-build-utils.ps1 + + # Check that the channel we are going to promote the build to exist + $channelInfo = Get-MaestroChannel -ChannelId $ChannelId + + if (!$channelInfo) { + Write-PipelineTelemetryCategory -Category 'PromoteBuild' -Message "Channel with BAR ID $ChannelId was not found in BAR!" + ExitWithExitCode 1 + } + + # Get info about which channel(s) the build has already been promoted to + $buildInfo = Get-MaestroBuild -BuildId $BuildId + + if (!$buildInfo) { + Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "Build with BAR ID $BuildId was not found in BAR!" + ExitWithExitCode 1 + } + + # Find whether the build is already assigned to the channel or not + if ($buildInfo.channels) { + foreach ($channel in $buildInfo.channels) { + if ($channel.Id -eq $ChannelId) { + Write-Host "The build with BAR ID $BuildId is already on channel $ChannelId!" + ExitWithExitCode 0 + } + } + } + + Write-Host "Promoting build '$BuildId' to channel '$ChannelId'." + + Assign-BuildToChannel -BuildId $BuildId -ChannelId $ChannelId + + Write-Host 'done.' +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "There was an error while trying to promote build '$BuildId' to channel '$ChannelId'" + ExitWithExitCode 1 +} diff --git a/eng/common/post-build/check-channel-consistency.ps1 b/eng/common/post-build/check-channel-consistency.ps1 new file mode 100644 index 000000000..63f3464c9 --- /dev/null +++ b/eng/common/post-build/check-channel-consistency.ps1 @@ -0,0 +1,40 @@ +param( + [Parameter(Mandatory=$true)][string] $PromoteToChannels, # List of channels that the build should be promoted to + [Parameter(Mandatory=$true)][array] $AvailableChannelIds # List of channel IDs available in the YAML implementation +) + +try { + . $PSScriptRoot\post-build-utils.ps1 + + if ($PromoteToChannels -eq "") { + Write-PipelineTaskError -Type 'warning' -Message "This build won't publish assets as it's not configured to any Maestro channel. If that wasn't intended use Darc to configure a default channel using add-default-channel for this branch or to promote it to a channel using add-build-to-channel. See https://github.com/dotnet/arcade/blob/master/Documentation/Darc.md#assigning-an-individual-build-to-a-channel for more info." + ExitWithExitCode 0 + } + + # Check that every channel that Maestro told to promote the build to + # is available in YAML + $PromoteToChannelsIds = $PromoteToChannels -split "\D" | Where-Object { $_ } + + $hasErrors = $false + + foreach ($id in $PromoteToChannelsIds) { + if (($id -ne 0) -and ($id -notin $AvailableChannelIds)) { + Write-PipelineTaskError -Message "Channel $id is not present in the post-build YAML configuration! This is an error scenario. Please contact @dnceng." + $hasErrors = $true + } + } + + # The `Write-PipelineTaskError` doesn't error the script and we might report several errors + # in the previous lines. The check below makes sure that we return an error state from the + # script if we reported any validation error + if ($hasErrors) { + ExitWithExitCode 1 + } + + Write-Host 'done.' +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Category 'CheckChannelConsistency' -Message "There was an error while trying to check consistency of Maestro default channels for the build and post-build YAML configuration." + ExitWithExitCode 1 +} diff --git a/eng/common/post-build/nuget-validation.ps1 b/eng/common/post-build/nuget-validation.ps1 new file mode 100644 index 000000000..dab3534ab --- /dev/null +++ b/eng/common/post-build/nuget-validation.ps1 @@ -0,0 +1,24 @@ +# This script validates NuGet package metadata information using this +# tool: https://github.com/NuGet/NuGetGallery/tree/jver-verify/src/VerifyMicrosoftPackage + +param( + [Parameter(Mandatory=$true)][string] $PackagesPath, # Path to where the packages to be validated are + [Parameter(Mandatory=$true)][string] $ToolDestinationPath # Where the validation tool should be downloaded to +) + +try { + . $PSScriptRoot\post-build-utils.ps1 + + $url = 'https://raw.githubusercontent.com/NuGet/NuGetGallery/3e25ad135146676bcab0050a516939d9958bfa5d/src/VerifyMicrosoftPackage/verify.ps1' + + New-Item -ItemType 'directory' -Path ${ToolDestinationPath} -Force + + Invoke-WebRequest $url -OutFile ${ToolDestinationPath}\verify.ps1 + + & ${ToolDestinationPath}\verify.ps1 ${PackagesPath}\*.nupkg +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'NuGetValidation' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/post-build/post-build-utils.ps1 b/eng/common/post-build/post-build-utils.ps1 new file mode 100644 index 000000000..534f6988d --- /dev/null +++ b/eng/common/post-build/post-build-utils.ps1 @@ -0,0 +1,91 @@ +# Most of the functions in this file require the variables `MaestroApiEndPoint`, +# `MaestroApiVersion` and `MaestroApiAccessToken` to be globally available. + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 + +# `tools.ps1` checks $ci to perform some actions. Since the post-build +# scripts don't necessarily execute in the same agent that run the +# build.ps1/sh script this variable isn't automatically set. +$ci = $true +$disableConfigureToolsetImport = $true +. $PSScriptRoot\..\tools.ps1 + +function Create-MaestroApiRequestHeaders([string]$ContentType = 'application/json') { + Validate-MaestroVars + + $headers = New-Object 'System.Collections.Generic.Dictionary[[String],[String]]' + $headers.Add('Accept', $ContentType) + $headers.Add('Authorization',"Bearer $MaestroApiAccessToken") + return $headers +} + +function Get-MaestroChannel([int]$ChannelId) { + Validate-MaestroVars + + $apiHeaders = Create-MaestroApiRequestHeaders + $apiEndpoint = "$MaestroApiEndPoint/api/channels/${ChannelId}?api-version=$MaestroApiVersion" + + $result = try { Invoke-WebRequest -Method Get -Uri $apiEndpoint -Headers $apiHeaders | ConvertFrom-Json } catch { Write-Host "Error: $_" } + return $result +} + +function Get-MaestroBuild([int]$BuildId) { + Validate-MaestroVars + + $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken + $apiEndpoint = "$MaestroApiEndPoint/api/builds/${BuildId}?api-version=$MaestroApiVersion" + + $result = try { return Invoke-WebRequest -Method Get -Uri $apiEndpoint -Headers $apiHeaders | ConvertFrom-Json } catch { Write-Host "Error: $_" } + return $result +} + +function Get-MaestroSubscriptions([string]$SourceRepository, [int]$ChannelId) { + Validate-MaestroVars + + $SourceRepository = [System.Web.HttpUtility]::UrlEncode($SourceRepository) + $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken + $apiEndpoint = "$MaestroApiEndPoint/api/subscriptions?sourceRepository=$SourceRepository&channelId=$ChannelId&api-version=$MaestroApiVersion" + + $result = try { Invoke-WebRequest -Method Get -Uri $apiEndpoint -Headers $apiHeaders | ConvertFrom-Json } catch { Write-Host "Error: $_" } + return $result +} + +function Assign-BuildToChannel([int]$BuildId, [int]$ChannelId) { + Validate-MaestroVars + + $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken + $apiEndpoint = "$MaestroApiEndPoint/api/channels/${ChannelId}/builds/${BuildId}?api-version=$MaestroApiVersion" + Invoke-WebRequest -Method Post -Uri $apiEndpoint -Headers $apiHeaders | Out-Null +} + +function Trigger-Subscription([string]$SubscriptionId) { + Validate-MaestroVars + + $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken + $apiEndpoint = "$MaestroApiEndPoint/api/subscriptions/$SubscriptionId/trigger?api-version=$MaestroApiVersion" + Invoke-WebRequest -Uri $apiEndpoint -Headers $apiHeaders -Method Post | Out-Null +} + +function Validate-MaestroVars { + try { + Get-Variable MaestroApiEndPoint | Out-Null + Get-Variable MaestroApiVersion | Out-Null + Get-Variable MaestroApiAccessToken | Out-Null + + if (!($MaestroApiEndPoint -Match '^http[s]?://maestro-(int|prod).westus2.cloudapp.azure.com$')) { + Write-PipelineTelemetryError -Category 'MaestroVars' -Message "MaestroApiEndPoint is not a valid Maestro URL. '$MaestroApiEndPoint'" + ExitWithExitCode 1 + } + + if (!($MaestroApiVersion -Match '^[0-9]{4}-[0-9]{2}-[0-9]{2}$')) { + Write-PipelineTelemetryError -Category 'MaestroVars' -Message "MaestroApiVersion does not match a version string in the format yyyy-MM-DD. '$MaestroApiVersion'" + ExitWithExitCode 1 + } + } + catch { + Write-PipelineTelemetryError -Category 'MaestroVars' -Message 'Error: Variables `MaestroApiEndPoint`, `MaestroApiVersion` and `MaestroApiAccessToken` are required while using this script.' + Write-Host $_ + ExitWithExitCode 1 + } +} diff --git a/eng/common/post-build/publish-using-darc.ps1 b/eng/common/post-build/publish-using-darc.ps1 new file mode 100644 index 000000000..2427ca6b6 --- /dev/null +++ b/eng/common/post-build/publish-using-darc.ps1 @@ -0,0 +1,80 @@ +param( + [Parameter(Mandatory=$true)][int] $BuildId, + [Parameter(Mandatory=$true)][int] $PublishingInfraVersion, + [Parameter(Mandatory=$true)][string] $AzdoToken, + [Parameter(Mandatory=$true)][string] $MaestroToken, + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro-prod.westus2.cloudapp.azure.com', + [Parameter(Mandatory=$true)][string] $WaitPublishingFinish, + [Parameter(Mandatory=$false)][string] $EnableSourceLinkValidation, + [Parameter(Mandatory=$false)][string] $EnableSigningValidation, + [Parameter(Mandatory=$false)][string] $EnableNugetValidation, + [Parameter(Mandatory=$false)][string] $PublishInstallersAndChecksums, + [Parameter(Mandatory=$false)][string] $ArtifactsPublishingAdditionalParameters, + [Parameter(Mandatory=$false)][string] $SymbolPublishingAdditionalParameters, + [Parameter(Mandatory=$false)][string] $SigningValidationAdditionalParameters +) + +try { + . $PSScriptRoot\post-build-utils.ps1 + + $darc = Get-Darc + + $optionalParams = [System.Collections.ArrayList]::new() + + if ("" -ne $ArtifactsPublishingAdditionalParameters) { + $optionalParams.Add("--artifact-publishing-parameters") | Out-Null + $optionalParams.Add($ArtifactsPublishingAdditionalParameters) | Out-Null + } + + if ("" -ne $SymbolPublishingAdditionalParameters) { + $optionalParams.Add("--symbol-publishing-parameters") | Out-Null + $optionalParams.Add($SymbolPublishingAdditionalParameters) | Out-Null + } + + if ("false" -eq $WaitPublishingFinish) { + $optionalParams.Add("--no-wait") | Out-Null + } + + if ("false" -ne $PublishInstallersAndChecksums) { + $optionalParams.Add("--publish-installers-and-checksums") | Out-Null + } + + if ("true" -eq $EnableNugetValidation) { + $optionalParams.Add("--validate-nuget") | Out-Null + } + + if ("true" -eq $EnableSourceLinkValidation) { + $optionalParams.Add("--validate-sourcelinkchecksums") | Out-Null + } + + if ("true" -eq $EnableSigningValidation) { + $optionalParams.Add("--validate-signingchecksums") | Out-Null + + if ("" -ne $SigningValidationAdditionalParameters) { + $optionalParams.Add("--signing-validation-parameters") | Out-Null + $optionalParams.Add($SigningValidationAdditionalParameters) | Out-Null + } + } + + & $darc add-build-to-channel ` + --id $buildId ` + --publishing-infra-version $PublishingInfraVersion ` + --default-channels ` + --source-branch main ` + --azdev-pat $AzdoToken ` + --bar-uri $MaestroApiEndPoint ` + --password $MaestroToken ` + @optionalParams + + if ($LastExitCode -ne 0) { + Write-Host "Problems using Darc to promote build ${buildId} to default channels. Stopping execution..." + exit 1 + } + + Write-Host 'done.' +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "There was an error while trying to publish build '$BuildId' to default channels." + ExitWithExitCode 1 +} diff --git a/eng/common/post-build/sourcelink-validation.ps1 b/eng/common/post-build/sourcelink-validation.ps1 new file mode 100644 index 000000000..85c898617 --- /dev/null +++ b/eng/common/post-build/sourcelink-validation.ps1 @@ -0,0 +1,303 @@ +param( + [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where Symbols.NuGet packages to be checked are stored + [Parameter(Mandatory=$true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation + [Parameter(Mandatory=$false)][string] $GHRepoName, # GitHub name of the repo including the Org. E.g., dotnet/arcade + [Parameter(Mandatory=$false)][string] $GHCommit, # GitHub commit SHA used to build the packages + [Parameter(Mandatory=$true)][string] $SourcelinkCliVersion # Version of SourceLink CLI to use +) + +. $PSScriptRoot\post-build-utils.ps1 + +# Cache/HashMap (File -> Exist flag) used to consult whether a file exist +# in the repository at a specific commit point. This is populated by inserting +# all files present in the repo at a specific commit point. +$global:RepoFiles = @{} + +# Maximum number of jobs to run in parallel +$MaxParallelJobs = 16 + +$MaxRetries = 5 + +# Wait time between check for system load +$SecondsBetweenLoadChecks = 10 + +$ValidatePackage = { + param( + [string] $PackagePath # Full path to a Symbols.NuGet package + ) + + . $using:PSScriptRoot\..\tools.ps1 + + # Ensure input file exist + if (!(Test-Path $PackagePath)) { + Write-Host "Input file does not exist: $PackagePath" + return [pscustomobject]@{ + result = 1 + packagePath = $PackagePath + } + } + + # Extensions for which we'll look for SourceLink information + # For now we'll only care about Portable & Embedded PDBs + $RelevantExtensions = @('.dll', '.exe', '.pdb') + + Write-Host -NoNewLine 'Validating ' ([System.IO.Path]::GetFileName($PackagePath)) '...' + + $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) + $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId + $FailedFiles = 0 + + Add-Type -AssemblyName System.IO.Compression.FileSystem + + [System.IO.Directory]::CreateDirectory($ExtractPath) | Out-Null + + try { + $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) + + $zip.Entries | + Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | + ForEach-Object { + $FileName = $_.FullName + $Extension = [System.IO.Path]::GetExtension($_.Name) + $FakeName = -Join((New-Guid), $Extension) + $TargetFile = Join-Path -Path $ExtractPath -ChildPath $FakeName + + # We ignore resource DLLs + if ($FileName.EndsWith('.resources.dll')) { + return [pscustomobject]@{ + result = 0 + packagePath = $PackagePath + } + } + + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true) + + $ValidateFile = { + param( + [string] $FullPath, # Full path to the module that has to be checked + [string] $RealPath, + [ref] $FailedFiles + ) + + $sourcelinkExe = "$env:USERPROFILE\.dotnet\tools" + $sourcelinkExe = Resolve-Path "$sourcelinkExe\sourcelink.exe" + $SourceLinkInfos = & $sourcelinkExe print-urls $FullPath | Out-String + + if ($LASTEXITCODE -eq 0 -and -not ([string]::IsNullOrEmpty($SourceLinkInfos))) { + $NumFailedLinks = 0 + + # We only care about Http addresses + $Matches = (Select-String '(http[s]?)(:\/\/)([^\s,]+)' -Input $SourceLinkInfos -AllMatches).Matches + + if ($Matches.Count -ne 0) { + $Matches.Value | + ForEach-Object { + $Link = $_ + $CommitUrl = "https://raw.githubusercontent.com/${using:GHRepoName}/${using:GHCommit}/" + + $FilePath = $Link.Replace($CommitUrl, "") + $Status = 200 + $Cache = $using:RepoFiles + + $totalRetries = 0 + + while ($totalRetries -lt $using:MaxRetries) { + if ( !($Cache.ContainsKey($FilePath)) ) { + try { + $Uri = $Link -as [System.URI] + + # Only GitHub links are valid + if ($Uri.AbsoluteURI -ne $null -and ($Uri.Host -match 'github' -or $Uri.Host -match 'githubusercontent')) { + $Status = (Invoke-WebRequest -Uri $Link -UseBasicParsing -Method HEAD -TimeoutSec 5).StatusCode + } + else { + # If it's not a github link, we want to break out of the loop and not retry. + $Status = 0 + $totalRetries = $using:MaxRetries + } + } + catch { + Write-Host $_ + $Status = 0 + } + } + + if ($Status -ne 200) { + $totalRetries++ + + if ($totalRetries -ge $using:MaxRetries) { + if ($NumFailedLinks -eq 0) { + if ($FailedFiles.Value -eq 0) { + Write-Host + } + + Write-Host "`tFile $RealPath has broken links:" + } + + Write-Host "`t`tFailed to retrieve $Link" + + $NumFailedLinks++ + } + } + else { + break + } + } + } + } + + if ($NumFailedLinks -ne 0) { + $FailedFiles.value++ + $global:LASTEXITCODE = 1 + } + } + } + + &$ValidateFile $TargetFile $FileName ([ref]$FailedFiles) + } + } + catch { + Write-Host $_ + } + finally { + $zip.Dispose() + } + + if ($FailedFiles -eq 0) { + Write-Host 'Passed.' + return [pscustomobject]@{ + result = 0 + packagePath = $PackagePath + } + } + else { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "$PackagePath has broken SourceLink links." + return [pscustomobject]@{ + result = 1 + packagePath = $PackagePath + } + } +} + +function CheckJobResult( + $result, + $packagePath, + [ref]$ValidationFailures, + [switch]$logErrors) { + if ($result -ne '0') { + if ($logErrors) { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "$packagePath has broken SourceLink links." + } + $ValidationFailures.Value++ + } +} + +function ValidateSourceLinkLinks { + if ($GHRepoName -ne '' -and !($GHRepoName -Match '^[^\s\/]+/[^\s\/]+$')) { + if (!($GHRepoName -Match '^[^\s-]+-[^\s]+$')) { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "GHRepoName should be in the format / or -. '$GHRepoName'" + ExitWithExitCode 1 + } + else { + $GHRepoName = $GHRepoName -replace '^([^\s-]+)-([^\s]+)$', '$1/$2'; + } + } + + if ($GHCommit -ne '' -and !($GHCommit -Match '^[0-9a-fA-F]{40}$')) { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "GHCommit should be a 40 chars hexadecimal string. '$GHCommit'" + ExitWithExitCode 1 + } + + if ($GHRepoName -ne '' -and $GHCommit -ne '') { + $RepoTreeURL = -Join('http://api.github.com/repos/', $GHRepoName, '/git/trees/', $GHCommit, '?recursive=1') + $CodeExtensions = @('.cs', '.vb', '.fs', '.fsi', '.fsx', '.fsscript') + + try { + # Retrieve the list of files in the repo at that particular commit point and store them in the RepoFiles hash + $Data = Invoke-WebRequest $RepoTreeURL -UseBasicParsing | ConvertFrom-Json | Select-Object -ExpandProperty tree + + foreach ($file in $Data) { + $Extension = [System.IO.Path]::GetExtension($file.path) + + if ($CodeExtensions.Contains($Extension)) { + $RepoFiles[$file.path] = 1 + } + } + } + catch { + Write-Host "Problems downloading the list of files from the repo. Url used: $RepoTreeURL . Execution will proceed without caching." + } + } + elseif ($GHRepoName -ne '' -or $GHCommit -ne '') { + Write-Host 'For using the http caching mechanism both GHRepoName and GHCommit should be informed.' + } + + if (Test-Path $ExtractPath) { + Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue + } + + $ValidationFailures = 0 + + # Process each NuGet package in parallel + Get-ChildItem "$InputPath\*.symbols.nupkg" | + ForEach-Object { + Write-Host "Starting $($_.FullName)" + Start-Job -ScriptBlock $ValidatePackage -ArgumentList $_.FullName | Out-Null + $NumJobs = @(Get-Job -State 'Running').Count + + while ($NumJobs -ge $MaxParallelJobs) { + Write-Host "There are $NumJobs validation jobs running right now. Waiting $SecondsBetweenLoadChecks seconds to check again." + sleep $SecondsBetweenLoadChecks + $NumJobs = @(Get-Job -State 'Running').Count + } + + foreach ($Job in @(Get-Job -State 'Completed')) { + $jobResult = Wait-Job -Id $Job.Id | Receive-Job + CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$ValidationFailures) -LogErrors + Remove-Job -Id $Job.Id + } + } + + foreach ($Job in @(Get-Job)) { + $jobResult = Wait-Job -Id $Job.Id | Receive-Job + CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$ValidationFailures) + Remove-Job -Id $Job.Id + } + if ($ValidationFailures -gt 0) { + Write-PipelineTelemetryError -Category 'SourceLink' -Message "$ValidationFailures package(s) failed validation." + ExitWithExitCode 1 + } +} + +function InstallSourcelinkCli { + $sourcelinkCliPackageName = 'sourcelink' + + $dotnetRoot = InitializeDotNetCli -install:$true + $dotnet = "$dotnetRoot\dotnet.exe" + $toolList = & "$dotnet" tool list --global + + if (($toolList -like "*$sourcelinkCliPackageName*") -and ($toolList -like "*$sourcelinkCliVersion*")) { + Write-Host "SourceLink CLI version $sourcelinkCliVersion is already installed." + } + else { + Write-Host "Installing SourceLink CLI version $sourcelinkCliVersion..." + Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' + & "$dotnet" tool install $sourcelinkCliPackageName --version $sourcelinkCliVersion --verbosity "minimal" --global + } +} + +try { + InstallSourcelinkCli + + foreach ($Job in @(Get-Job)) { + Remove-Job -Id $Job.Id + } + + ValidateSourceLinkLinks +} +catch { + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'SourceLink' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/post-build/symbols-validation.ps1 b/eng/common/post-build/symbols-validation.ps1 new file mode 100644 index 000000000..a5af041ba --- /dev/null +++ b/eng/common/post-build/symbols-validation.ps1 @@ -0,0 +1,316 @@ +param( + [Parameter(Mandatory = $true)][string] $InputPath, # Full path to directory where NuGet packages to be checked are stored + [Parameter(Mandatory = $true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation + [Parameter(Mandatory = $true)][string] $DotnetSymbolVersion, # Version of dotnet symbol to use + [Parameter(Mandatory = $false)][switch] $CheckForWindowsPdbs, # If we should check for the existence of windows pdbs in addition to portable PDBs + [Parameter(Mandatory = $false)][switch] $ContinueOnError, # If we should keep checking symbols after an error + [Parameter(Mandatory = $false)][switch] $Clean # Clean extracted symbols directory after checking symbols +) + +# Maximum number of jobs to run in parallel +$MaxParallelJobs = 16 + +# Max number of retries +$MaxRetry = 5 + +# Wait time between check for system load +$SecondsBetweenLoadChecks = 10 + +# Set error codes +Set-Variable -Name "ERROR_BADEXTRACT" -Option Constant -Value -1 +Set-Variable -Name "ERROR_FILEDOESNOTEXIST" -Option Constant -Value -2 + +$WindowsPdbVerificationParam = "" +if ($CheckForWindowsPdbs) { + $WindowsPdbVerificationParam = "--windows-pdbs" +} + +$CountMissingSymbols = { + param( + [string] $PackagePath, # Path to a NuGet package + [string] $WindowsPdbVerificationParam # If we should check for the existence of windows pdbs in addition to portable PDBs + ) + + . $using:PSScriptRoot\..\tools.ps1 + + Add-Type -AssemblyName System.IO.Compression.FileSystem + + Write-Host "Validating $PackagePath " + + # Ensure input file exist + if (!(Test-Path $PackagePath)) { + Write-PipelineTaskError "Input file does not exist: $PackagePath" + return [pscustomobject]@{ + result = $using:ERROR_FILEDOESNOTEXIST + packagePath = $PackagePath + } + } + + # Extensions for which we'll look for symbols + $RelevantExtensions = @('.dll', '.exe', '.so', '.dylib') + + # How many files are missing symbol information + $MissingSymbols = 0 + + $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) + $PackageGuid = New-Guid + $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageGuid + $SymbolsPath = Join-Path -Path $ExtractPath -ChildPath 'Symbols' + + try { + [System.IO.Compression.ZipFile]::ExtractToDirectory($PackagePath, $ExtractPath) + } + catch { + Write-Host "Something went wrong extracting $PackagePath" + Write-Host $_ + return [pscustomobject]@{ + result = $using:ERROR_BADEXTRACT + packagePath = $PackagePath + } + } + + Get-ChildItem -Recurse $ExtractPath | + Where-Object { $RelevantExtensions -contains $_.Extension } | + ForEach-Object { + $FileName = $_.FullName + if ($FileName -Match '\\ref\\') { + Write-Host "`t Ignoring reference assembly file " $FileName + return + } + + $FirstMatchingSymbolDescriptionOrDefault = { + param( + [string] $FullPath, # Full path to the module that has to be checked + [string] $TargetServerParam, # Parameter to pass to `Symbol Tool` indicating the server to lookup for symbols + [string] $WindowsPdbVerificationParam, # Parameter to pass to potential check for windows-pdbs. + [string] $SymbolsPath + ) + + $FileName = [System.IO.Path]::GetFileName($FullPath) + $Extension = [System.IO.Path]::GetExtension($FullPath) + + # Those below are potential symbol files that the `dotnet symbol` might + # return. Which one will be returned depend on the type of file we are + # checking and which type of file was uploaded. + + # The file itself is returned + $SymbolPath = $SymbolsPath + '\' + $FileName + + # PDB file for the module + $PdbPath = $SymbolPath.Replace($Extension, '.pdb') + + # PDB file for R2R module (created by crossgen) + $NGenPdb = $SymbolPath.Replace($Extension, '.ni.pdb') + + # DBG file for a .so library + $SODbg = $SymbolPath.Replace($Extension, '.so.dbg') + + # DWARF file for a .dylib + $DylibDwarf = $SymbolPath.Replace($Extension, '.dylib.dwarf') + + $dotnetSymbolExe = "$env:USERPROFILE\.dotnet\tools" + $dotnetSymbolExe = Resolve-Path "$dotnetSymbolExe\dotnet-symbol.exe" + + $totalRetries = 0 + + while ($totalRetries -lt $using:MaxRetry) { + + # Save the output and get diagnostic output + $output = & $dotnetSymbolExe --symbols --modules $WindowsPdbVerificationParam $TargetServerParam $FullPath -o $SymbolsPath --diagnostics | Out-String + + if (Test-Path $PdbPath) { + return 'PDB' + } + elseif (Test-Path $NGenPdb) { + return 'NGen PDB' + } + elseif (Test-Path $SODbg) { + return 'DBG for SO' + } + elseif (Test-Path $DylibDwarf) { + return 'Dwarf for Dylib' + } + elseif (Test-Path $SymbolPath) { + return 'Module' + } + else + { + $totalRetries++ + } + } + + return $null + } + + $FileGuid = New-Guid + $ExpandedSymbolsPath = Join-Path -Path $SymbolsPath -ChildPath $FileGuid + + $SymbolsOnMSDL = & $FirstMatchingSymbolDescriptionOrDefault ` + -FullPath $FileName ` + -TargetServerParam '--microsoft-symbol-server' ` + -SymbolsPath "$ExpandedSymbolsPath-msdl" ` + -WindowsPdbVerificationParam $WindowsPdbVerificationParam + $SymbolsOnSymWeb = & $FirstMatchingSymbolDescriptionOrDefault ` + -FullPath $FileName ` + -TargetServerParam '--internal-server' ` + -SymbolsPath "$ExpandedSymbolsPath-symweb" ` + -WindowsPdbVerificationParam $WindowsPdbVerificationParam + + Write-Host -NoNewLine "`t Checking file " $FileName "... " + + if ($SymbolsOnMSDL -ne $null -and $SymbolsOnSymWeb -ne $null) { + Write-Host "Symbols found on MSDL ($SymbolsOnMSDL) and SymWeb ($SymbolsOnSymWeb)" + } + else { + $MissingSymbols++ + + if ($SymbolsOnMSDL -eq $null -and $SymbolsOnSymWeb -eq $null) { + Write-Host 'No symbols found on MSDL or SymWeb!' + } + else { + if ($SymbolsOnMSDL -eq $null) { + Write-Host 'No symbols found on MSDL!' + } + else { + Write-Host 'No symbols found on SymWeb!' + } + } + } + } + + if ($using:Clean) { + Remove-Item $ExtractPath -Recurse -Force + } + + Pop-Location + + return [pscustomobject]@{ + result = $MissingSymbols + packagePath = $PackagePath + } +} + +function CheckJobResult( + $result, + $packagePath, + [ref]$DupedSymbols, + [ref]$TotalFailures) { + if ($result -eq $ERROR_BADEXTRACT) { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$packagePath has duplicated symbol files" + $DupedSymbols.Value++ + } + elseif ($result -eq $ERROR_FILEDOESNOTEXIST) { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$packagePath does not exist" + $TotalFailures.Value++ + } + elseif ($result -gt '0') { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "Missing symbols for $result modules in the package $packagePath" + $TotalFailures.Value++ + } + else { + Write-Host "All symbols verified for package $packagePath" + } +} + +function CheckSymbolsAvailable { + if (Test-Path $ExtractPath) { + Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue + } + + $TotalPackages = 0 + $TotalFailures = 0 + $DupedSymbols = 0 + + Get-ChildItem "$InputPath\*.nupkg" | + ForEach-Object { + $FileName = $_.Name + $FullName = $_.FullName + + # These packages from Arcade-Services include some native libraries that + # our current symbol uploader can't handle. Below is a workaround until + # we get issue: https://github.com/dotnet/arcade/issues/2457 sorted. + if ($FileName -Match 'Microsoft\.DotNet\.Darc\.') { + Write-Host "Ignoring Arcade-services file: $FileName" + Write-Host + return + } + elseif ($FileName -Match 'Microsoft\.DotNet\.Maestro\.Tasks\.') { + Write-Host "Ignoring Arcade-services file: $FileName" + Write-Host + return + } + + $TotalPackages++ + + Start-Job -ScriptBlock $CountMissingSymbols -ArgumentList @($FullName,$WindowsPdbVerificationParam) | Out-Null + + $NumJobs = @(Get-Job -State 'Running').Count + + while ($NumJobs -ge $MaxParallelJobs) { + Write-Host "There are $NumJobs validation jobs running right now. Waiting $SecondsBetweenLoadChecks seconds to check again." + sleep $SecondsBetweenLoadChecks + $NumJobs = @(Get-Job -State 'Running').Count + } + + foreach ($Job in @(Get-Job -State 'Completed')) { + $jobResult = Wait-Job -Id $Job.Id | Receive-Job + CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$DupedSymbols) ([ref]$TotalFailures) + Remove-Job -Id $Job.Id + } + Write-Host + } + + foreach ($Job in @(Get-Job)) { + $jobResult = Wait-Job -Id $Job.Id | Receive-Job + CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$DupedSymbols) ([ref]$TotalFailures) + } + + if ($TotalFailures -gt 0 -or $DupedSymbols -gt 0) { + if ($TotalFailures -gt 0) { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "Symbols missing for $TotalFailures/$TotalPackages packages" + } + + if ($DupedSymbols -gt 0) { + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "$DupedSymbols/$TotalPackages packages had duplicated symbol files and could not be extracted" + } + + ExitWithExitCode 1 + } + else { + Write-Host "All symbols validated!" + } +} + +function InstallDotnetSymbol { + $dotnetSymbolPackageName = 'dotnet-symbol' + + $dotnetRoot = InitializeDotNetCli -install:$true + $dotnet = "$dotnetRoot\dotnet.exe" + $toolList = & "$dotnet" tool list --global + + if (($toolList -like "*$dotnetSymbolPackageName*") -and ($toolList -like "*$dotnetSymbolVersion*")) { + Write-Host "dotnet-symbol version $dotnetSymbolVersion is already installed." + } + else { + Write-Host "Installing dotnet-symbol version $dotnetSymbolVersion..." + Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' + & "$dotnet" tool install $dotnetSymbolPackageName --version $dotnetSymbolVersion --verbosity "minimal" --global + } +} + +try { + . $PSScriptRoot\post-build-utils.ps1 + + InstallDotnetSymbol + + foreach ($Job in @(Get-Job)) { + Remove-Job -Id $Job.Id + } + + CheckSymbolsAvailable +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'CheckSymbols' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/post-build/trigger-subscriptions.ps1 b/eng/common/post-build/trigger-subscriptions.ps1 new file mode 100644 index 000000000..55dea518a --- /dev/null +++ b/eng/common/post-build/trigger-subscriptions.ps1 @@ -0,0 +1,64 @@ +param( + [Parameter(Mandatory=$true)][string] $SourceRepo, + [Parameter(Mandatory=$true)][int] $ChannelId, + [Parameter(Mandatory=$true)][string] $MaestroApiAccessToken, + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro-prod.westus2.cloudapp.azure.com', + [Parameter(Mandatory=$false)][string] $MaestroApiVersion = '2019-01-16' +) + +try { + . $PSScriptRoot\post-build-utils.ps1 + + # Get all the $SourceRepo subscriptions + $normalizedSourceRepo = $SourceRepo.Replace('dnceng@', '') + $subscriptions = Get-MaestroSubscriptions -SourceRepository $normalizedSourceRepo -ChannelId $ChannelId + + if (!$subscriptions) { + Write-PipelineTelemetryError -Category 'TriggerSubscriptions' -Message "No subscriptions found for source repo '$normalizedSourceRepo' in channel '$ChannelId'" + ExitWithExitCode 0 + } + + $subscriptionsToTrigger = New-Object System.Collections.Generic.List[string] + $failedTriggeredSubscription = $false + + # Get all enabled subscriptions that need dependency flow on 'everyBuild' + foreach ($subscription in $subscriptions) { + if ($subscription.enabled -and $subscription.policy.updateFrequency -like 'everyBuild' -and $subscription.channel.id -eq $ChannelId) { + Write-Host "Should trigger this subscription: ${$subscription.id}" + [void]$subscriptionsToTrigger.Add($subscription.id) + } + } + + foreach ($subscriptionToTrigger in $subscriptionsToTrigger) { + try { + Write-Host "Triggering subscription '$subscriptionToTrigger'." + + Trigger-Subscription -SubscriptionId $subscriptionToTrigger + + Write-Host 'done.' + } + catch + { + Write-Host "There was an error while triggering subscription '$subscriptionToTrigger'" + Write-Host $_ + Write-Host $_.ScriptStackTrace + $failedTriggeredSubscription = $true + } + } + + if ($subscriptionsToTrigger.Count -eq 0) { + Write-Host "No subscription matched source repo '$normalizedSourceRepo' and channel ID '$ChannelId'." + } + elseif ($failedTriggeredSubscription) { + Write-PipelineTelemetryError -Category 'TriggerSubscriptions' -Message 'At least one subscription failed to be triggered...' + ExitWithExitCode 1 + } + else { + Write-Host 'All subscriptions were triggered successfully!' + } +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'TriggerSubscriptions' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/sdk-task.ps1 b/eng/common/sdk-task.ps1 new file mode 100644 index 000000000..65f1d75f3 --- /dev/null +++ b/eng/common/sdk-task.ps1 @@ -0,0 +1,97 @@ +[CmdletBinding(PositionalBinding=$false)] +Param( + [string] $configuration = 'Debug', + [string] $task, + [string] $verbosity = 'minimal', + [string] $msbuildEngine = $null, + [switch] $restore, + [switch] $prepareMachine, + [switch] $help, + [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties +) + +$ci = $true +$binaryLog = $true +$warnAsError = $true + +. $PSScriptRoot\tools.ps1 + +function Print-Usage() { + Write-Host "Common settings:" + Write-Host " -task Name of Arcade task (name of a project in SdkTasks directory of the Arcade SDK package)" + Write-Host " -restore Restore dependencies" + Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic]" + Write-Host " -help Print help and exit" + Write-Host "" + + Write-Host "Advanced settings:" + Write-Host " -prepareMachine Prepare machine for CI run" + Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)." + Write-Host "" + Write-Host "Command line arguments not listed above are passed thru to msbuild." +} + +function Build([string]$target) { + $logSuffix = if ($target -eq 'Execute') { '' } else { ".$target" } + $log = Join-Path $LogDir "$task$logSuffix.binlog" + $outputPath = Join-Path $ToolsetDir "$task\\" + + MSBuild $taskProject ` + /bl:$log ` + /t:$target ` + /p:Configuration=$configuration ` + /p:RepoRoot=$RepoRoot ` + /p:BaseIntermediateOutputPath=$outputPath ` + /v:$verbosity ` + @properties +} + +try { + if ($help -or (($null -ne $properties) -and ($properties.Contains('/help') -or $properties.Contains('/?')))) { + Print-Usage + exit 0 + } + + if ($task -eq "") { + Write-PipelineTelemetryError -Category 'Build' -Message "Missing required parameter '-task '" + Print-Usage + ExitWithExitCode 1 + } + + if( $msbuildEngine -eq "vs") { + # Ensure desktop MSBuild is available for sdk tasks. + if( -not ($GlobalJson.tools.PSObject.Properties.Name -contains "vs" )) { + $GlobalJson.tools | Add-Member -Name "vs" -Value (ConvertFrom-Json "{ `"version`": `"16.5`" }") -MemberType NoteProperty + } + if( -not ($GlobalJson.tools.PSObject.Properties.Name -match "xcopy-msbuild" )) { + $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "16.8.0-preview3" -MemberType NoteProperty + } + if ($GlobalJson.tools."xcopy-msbuild".Trim() -ine "none") { + $xcopyMSBuildToolsFolder = InitializeXCopyMSBuild $GlobalJson.tools."xcopy-msbuild" -install $true + } + if ($xcopyMSBuildToolsFolder -eq $null) { + throw 'Unable to get xcopy downloadable version of msbuild' + } + + $global:_MSBuildExe = "$($xcopyMSBuildToolsFolder)\MSBuild\Current\Bin\MSBuild.exe" + } + + $taskProject = GetSdkTaskProject $task + if (!(Test-Path $taskProject)) { + Write-PipelineTelemetryError -Category 'Build' -Message "Unknown task: $task" + ExitWithExitCode 1 + } + + if ($restore) { + Build 'Restore' + } + + Build 'Execute' +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Category 'Build' -Message $_ + ExitWithExitCode 1 +} + +ExitWithExitCode 0 diff --git a/eng/common/sdl/NuGet.config b/eng/common/sdl/NuGet.config new file mode 100644 index 000000000..0c5451c11 --- /dev/null +++ b/eng/common/sdl/NuGet.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/eng/common/sdl/execute-all-sdl-tools.ps1 b/eng/common/sdl/execute-all-sdl-tools.ps1 new file mode 100644 index 000000000..81b729f74 --- /dev/null +++ b/eng/common/sdl/execute-all-sdl-tools.ps1 @@ -0,0 +1,116 @@ +Param( + [string] $GuardianPackageName, # Required: the name of guardian CLI package (not needed if GuardianCliLocation is specified) + [string] $NugetPackageDirectory, # Required: directory where NuGet packages are installed (not needed if GuardianCliLocation is specified) + [string] $GuardianCliLocation, # Optional: Direct location of Guardian CLI executable if GuardianPackageName & NugetPackageDirectory are not specified + [string] $Repository=$env:BUILD_REPOSITORY_NAME, # Required: the name of the repository (e.g. dotnet/arcade) + [string] $BranchName=$env:BUILD_SOURCEBRANCH, # Optional: name of branch or version of gdn settings; defaults to master + [string] $SourceDirectory=$env:BUILD_SOURCESDIRECTORY, # Required: the directory where source files are located + [string] $ArtifactsDirectory = (Join-Path $env:BUILD_ARTIFACTSTAGINGDIRECTORY ('artifacts')), # Required: the directory where build artifacts are located + [string] $AzureDevOpsAccessToken, # Required: access token for dnceng; should be provided via KeyVault + [string[]] $SourceToolsList, # Optional: list of SDL tools to run on source code + [string[]] $ArtifactToolsList, # Optional: list of SDL tools to run on built artifacts + [bool] $TsaPublish=$False, # Optional: true will publish results to TSA; only set to true after onboarding to TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaBranchName=$env:BUILD_SOURCEBRANCH, # Optional: required for TSA publish; defaults to $(Build.SourceBranchName); TSA is the automated framework used to upload test results as bugs. + [string] $TsaRepositoryName=$env:BUILD_REPOSITORY_NAME, # Optional: TSA repository name; will be generated automatically if not submitted; TSA is the automated framework used to upload test results as bugs. + [string] $BuildNumber=$env:BUILD_BUILDNUMBER, # Optional: required for TSA publish; defaults to $(Build.BuildNumber) + [bool] $UpdateBaseline=$False, # Optional: if true, will update the baseline in the repository; should only be run after fixing any issues which need to be fixed + [bool] $TsaOnboard=$False, # Optional: if true, will onboard the repository to TSA; should only be run once; TSA is the automated framework used to upload test results as bugs. + [string] $TsaInstanceUrl, # Optional: only needed if TsaOnboard or TsaPublish is true; the instance-url registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaCodebaseName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the codebase registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaProjectName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the project registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaNotificationEmail, # Optional: only needed if TsaOnboard is true; the email(s) which will receive notifications of TSA bug filings (e.g. alias@microsoft.com); TSA is the automated framework used to upload test results as bugs. + [string] $TsaCodebaseAdmin, # Optional: only needed if TsaOnboard is true; the aliases which are admins of the TSA codebase (e.g. DOMAIN\alias); TSA is the automated framework used to upload test results as bugs. + [string] $TsaBugAreaPath, # Optional: only needed if TsaOnboard is true; the area path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. + [string] $TsaIterationPath, # Optional: only needed if TsaOnboard is true; the iteration path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. + [string] $GuardianLoggerLevel='Standard', # Optional: the logger level for the Guardian CLI; options are Trace, Verbose, Standard, Warning, and Error + [string[]] $CrScanAdditionalRunConfigParams, # Optional: Additional Params to custom build a CredScan run config in the format @("xyz:abc","sdf:1") + [string[]] $PoliCheckAdditionalRunConfigParams, # Optional: Additional Params to custom build a Policheck run config in the format @("xyz:abc","sdf:1") + [bool] $BreakOnFailure=$False # Optional: Fail the build if there were errors during the run +) + +try { + $ErrorActionPreference = 'Stop' + Set-StrictMode -Version 2.0 + $disableConfigureToolsetImport = $true + $LASTEXITCODE = 0 + + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + #Replace repo names to the format of org/repo + if (!($Repository.contains('/'))) { + $RepoName = $Repository -replace '(.*?)-(.*)', '$1/$2'; + } + else{ + $RepoName = $Repository; + } + + if ($GuardianPackageName) { + $guardianCliLocation = Join-Path $NugetPackageDirectory (Join-Path $GuardianPackageName (Join-Path 'tools' 'guardian.cmd')) + } else { + $guardianCliLocation = $GuardianCliLocation + } + + $workingDirectory = (Split-Path $SourceDirectory -Parent) + $ValidPath = Test-Path $guardianCliLocation + + if ($ValidPath -eq $False) + { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Invalid Guardian CLI Location.' + ExitWithExitCode 1 + } + + & $(Join-Path $PSScriptRoot 'init-sdl.ps1') -GuardianCliLocation $guardianCliLocation -Repository $RepoName -BranchName $BranchName -WorkingDirectory $workingDirectory -AzureDevOpsAccessToken $AzureDevOpsAccessToken -GuardianLoggerLevel $GuardianLoggerLevel + $gdnFolder = Join-Path $workingDirectory '.gdn' + + if ($TsaOnboard) { + if ($TsaCodebaseName -and $TsaNotificationEmail -and $TsaCodebaseAdmin -and $TsaBugAreaPath) { + Write-Host "$guardianCliLocation tsa-onboard --codebase-name `"$TsaCodebaseName`" --notification-alias `"$TsaNotificationEmail`" --codebase-admin `"$TsaCodebaseAdmin`" --instance-url `"$TsaInstanceUrl`" --project-name `"$TsaProjectName`" --area-path `"$TsaBugAreaPath`" --iteration-path `"$TsaIterationPath`" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel" + & $guardianCliLocation tsa-onboard --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian tsa-onboard failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + } else { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Could not onboard to TSA -- not all required values ($TsaCodebaseName, $TsaNotificationEmail, $TsaCodebaseAdmin, $TsaBugAreaPath) were specified.' + ExitWithExitCode 1 + } + } + + if ($ArtifactToolsList -and $ArtifactToolsList.Count -gt 0) { + & $(Join-Path $PSScriptRoot 'run-sdl.ps1') -GuardianCliLocation $guardianCliLocation -WorkingDirectory $workingDirectory -TargetDirectory $ArtifactsDirectory -GdnFolder $gdnFolder -ToolsList $ArtifactToolsList -AzureDevOpsAccessToken $AzureDevOpsAccessToken -UpdateBaseline $UpdateBaseline -GuardianLoggerLevel $GuardianLoggerLevel -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams + } + if ($SourceToolsList -and $SourceToolsList.Count -gt 0) { + & $(Join-Path $PSScriptRoot 'run-sdl.ps1') -GuardianCliLocation $guardianCliLocation -WorkingDirectory $workingDirectory -TargetDirectory $SourceDirectory -GdnFolder $gdnFolder -ToolsList $SourceToolsList -AzureDevOpsAccessToken $AzureDevOpsAccessToken -UpdateBaseline $UpdateBaseline -GuardianLoggerLevel $GuardianLoggerLevel -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams + } + + if ($TsaPublish) { + if ($TsaBranchName -and $BuildNumber) { + if (-not $TsaRepositoryName) { + $TsaRepositoryName = "$($Repository)-$($BranchName)" + } + Write-Host "$guardianCliLocation tsa-publish --all-tools --repository-name `"$TsaRepositoryName`" --branch-name `"$TsaBranchName`" --build-number `"$BuildNumber`" --codebase-name `"$TsaCodebaseName`" --notification-alias `"$TsaNotificationEmail`" --codebase-admin `"$TsaCodebaseAdmin`" --instance-url `"$TsaInstanceUrl`" --project-name `"$TsaProjectName`" --area-path `"$TsaBugAreaPath`" --iteration-path `"$TsaIterationPath`" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel" + & $guardianCliLocation tsa-publish --all-tools --repository-name "$TsaRepositoryName" --branch-name "$TsaBranchName" --build-number "$BuildNumber" --onboard $True --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian tsa-publish failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + } else { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Could not publish to TSA -- not all required values ($TsaBranchName, $BuildNumber) were specified.' + ExitWithExitCode 1 + } + } + + if ($BreakOnFailure) { + Write-Host "Failing the build in case of breaking results..." + & $guardianCliLocation break + } +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + exit 1 +} diff --git a/eng/common/sdl/extract-artifact-packages.ps1 b/eng/common/sdl/extract-artifact-packages.ps1 new file mode 100644 index 000000000..7f28d9c59 --- /dev/null +++ b/eng/common/sdl/extract-artifact-packages.ps1 @@ -0,0 +1,80 @@ +param( + [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where artifact packages are stored + [Parameter(Mandatory=$true)][string] $ExtractPath # Full path to directory where the packages will be extracted +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 + +$disableConfigureToolsetImport = $true + +function ExtractArtifacts { + if (!(Test-Path $InputPath)) { + Write-Host "Input Path does not exist: $InputPath" + ExitWithExitCode 0 + } + $Jobs = @() + Get-ChildItem "$InputPath\*.nupkg" | + ForEach-Object { + $Jobs += Start-Job -ScriptBlock $ExtractPackage -ArgumentList $_.FullName + } + + foreach ($Job in $Jobs) { + Wait-Job -Id $Job.Id | Receive-Job + } +} + +try { + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + $ExtractPackage = { + param( + [string] $PackagePath # Full path to a NuGet package + ) + + if (!(Test-Path $PackagePath)) { + Write-PipelineTelemetryError -Category 'Build' -Message "Input file does not exist: $PackagePath" + ExitWithExitCode 1 + } + + $RelevantExtensions = @('.dll', '.exe', '.pdb') + Write-Host -NoNewLine 'Extracting ' ([System.IO.Path]::GetFileName($PackagePath)) '...' + + $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) + $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId + + Add-Type -AssemblyName System.IO.Compression.FileSystem + + [System.IO.Directory]::CreateDirectory($ExtractPath); + + try { + $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) + + $zip.Entries | + Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | + ForEach-Object { + $TargetFile = Join-Path -Path $ExtractPath -ChildPath $_.Name + + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true) + } + } + catch { + Write-Host $_ + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 + } + finally { + $zip.Dispose() + } + } + Measure-Command { ExtractArtifacts } +} +catch { + Write-Host $_ + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/sdl/init-sdl.ps1 b/eng/common/sdl/init-sdl.ps1 new file mode 100644 index 000000000..1fe927119 --- /dev/null +++ b/eng/common/sdl/init-sdl.ps1 @@ -0,0 +1,55 @@ +Param( + [string] $GuardianCliLocation, + [string] $Repository, + [string] $BranchName='master', + [string] $WorkingDirectory, + [string] $AzureDevOpsAccessToken, + [string] $GuardianLoggerLevel='Standard' +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 +$disableConfigureToolsetImport = $true +$LASTEXITCODE = 0 + +# `tools.ps1` checks $ci to perform some actions. Since the SDL +# scripts don't necessarily execute in the same agent that run the +# build.ps1/sh script this variable isn't automatically set. +$ci = $true +. $PSScriptRoot\..\tools.ps1 + +# Don't display the console progress UI - it's a huge perf hit +$ProgressPreference = 'SilentlyContinue' + +# Construct basic auth from AzDO access token; construct URI to the repository's gdn folder stored in that repository; construct location of zip file +$encodedPat = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$AzureDevOpsAccessToken")) +$escapedRepository = [Uri]::EscapeDataString("/$Repository/$BranchName/.gdn") +$uri = "https://dev.azure.com/dnceng/internal/_apis/git/repositories/sdl-tool-cfg/Items?path=$escapedRepository&versionDescriptor[versionOptions]=0&`$format=zip&api-version=5.0" +$zipFile = "$WorkingDirectory/gdn.zip" + +Add-Type -AssemblyName System.IO.Compression.FileSystem +$gdnFolder = (Join-Path $WorkingDirectory '.gdn') + +try { + # if the folder does not exist, we'll do a guardian init and push it to the remote repository + Write-Host 'Initializing Guardian...' + Write-Host "$GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel" + & $GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Build' -Message "Guardian init failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + # We create the mainbaseline so it can be edited later + Write-Host "$GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline" + & $GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Build' -Message "Guardian baseline failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + ExitWithExitCode 0 +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/sdl/packages.config b/eng/common/sdl/packages.config new file mode 100644 index 000000000..3bd8b29eb --- /dev/null +++ b/eng/common/sdl/packages.config @@ -0,0 +1,4 @@ + + + + diff --git a/eng/common/sdl/run-sdl.ps1 b/eng/common/sdl/run-sdl.ps1 new file mode 100644 index 000000000..fe95ab35a --- /dev/null +++ b/eng/common/sdl/run-sdl.ps1 @@ -0,0 +1,73 @@ +Param( + [string] $GuardianCliLocation, + [string] $WorkingDirectory, + [string] $TargetDirectory, + [string] $GdnFolder, + [string[]] $ToolsList, + [string] $UpdateBaseline, + [string] $GuardianLoggerLevel='Standard', + [string[]] $CrScanAdditionalRunConfigParams, + [string[]] $PoliCheckAdditionalRunConfigParams +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version 2.0 +$disableConfigureToolsetImport = $true +$LASTEXITCODE = 0 + +try { + # `tools.ps1` checks $ci to perform some actions. Since the SDL + # scripts don't necessarily execute in the same agent that run the + # build.ps1/sh script this variable isn't automatically set. + $ci = $true + . $PSScriptRoot\..\tools.ps1 + + # We store config files in the r directory of .gdn + Write-Host $ToolsList + $gdnConfigPath = Join-Path $GdnFolder 'r' + $ValidPath = Test-Path $GuardianCliLocation + + if ($ValidPath -eq $False) + { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Invalid Guardian CLI Location." + ExitWithExitCode 1 + } + + $configParam = @('--config') + + foreach ($tool in $ToolsList) { + $gdnConfigFile = Join-Path $gdnConfigPath "$tool-configure.gdnconfig" + Write-Host $tool + # We have to manually configure tools that run on source to look at the source directory only + if ($tool -eq 'credscan') { + Write-Host "$GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args `" TargetDirectory < $TargetDirectory `" `" OutputType < pre `" $(If ($CrScanAdditionalRunConfigParams) {$CrScanAdditionalRunConfigParams})" + & $GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args " TargetDirectory < $TargetDirectory " "OutputType < pre" $(If ($CrScanAdditionalRunConfigParams) {$CrScanAdditionalRunConfigParams}) + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian configure for $tool failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + } + if ($tool -eq 'policheck') { + Write-Host "$GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args `" Target < $TargetDirectory `" $(If ($PoliCheckAdditionalRunConfigParams) {$PoliCheckAdditionalRunConfigParams})" + & $GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args " Target < $TargetDirectory " $(If ($PoliCheckAdditionalRunConfigParams) {$PoliCheckAdditionalRunConfigParams}) + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian configure for $tool failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } + } + + $configParam+=$gdnConfigFile + } + + Write-Host "$GuardianCliLocation run --working-directory $WorkingDirectory --baseline mainbaseline --update-baseline $UpdateBaseline --logger-level $GuardianLoggerLevel $configParam" + & $GuardianCliLocation run --working-directory $WorkingDirectory --tool $tool --baseline mainbaseline --update-baseline $UpdateBaseline --logger-level $GuardianLoggerLevel $configParam + if ($LASTEXITCODE -ne 0) { + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian run for $ToolsList using $configParam failed with exit code $LASTEXITCODE." + ExitWithExitCode $LASTEXITCODE + } +} +catch { + Write-Host $_.ScriptStackTrace + Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + ExitWithExitCode 1 +} diff --git a/eng/common/templates/job/execute-sdl.yml b/eng/common/templates/job/execute-sdl.yml new file mode 100644 index 000000000..4a32181fd --- /dev/null +++ b/eng/common/templates/job/execute-sdl.yml @@ -0,0 +1,93 @@ +parameters: + enable: 'false' # Whether the SDL validation job should execute or not + overrideParameters: '' # Optional: to override values for parameters. + additionalParameters: '' # Optional: parameters that need user specific values eg: '-SourceToolsList @("abc","def") -ArtifactToolsList @("ghi","jkl")' + # There is some sort of bug (has been reported) in Azure DevOps where if this parameter is named + # 'continueOnError', the parameter value is not correctly picked up. + # This can also be remedied by the caller (post-build.yml) if it does not use a nested parameter + sdlContinueOnError: false # optional: determines whether to continue the build if the step errors; + downloadArtifacts: true # optional: determines if the artifacts should be dowloaded + dependsOn: '' # Optional: dependencies of the job + artifactNames: '' # Optional: patterns supplied to DownloadBuildArtifacts + # Usage: + # artifactNames: + # - 'BlobArtifacts' + # - 'Artifacts_Windows_NT_Release' + +jobs: +- job: Run_SDL + dependsOn: ${{ parameters.dependsOn }} + displayName: Run SDL tool + condition: eq( ${{ parameters.enable }}, 'true') + variables: + - group: DotNet-VSTS-Bot + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] + pool: + name: Hosted VS2017 + steps: + - checkout: self + clean: true + - ${{ if ne(parameters.downloadArtifacts, 'false')}}: + - ${{ if ne(parameters.artifactNames, '') }}: + - ${{ each artifactName in parameters.artifactNames }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Build Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: ${{ artifactName }} + downloadPath: $(Build.ArtifactStagingDirectory)\artifacts + checkDownloadedFiles: true + - ${{ if eq(parameters.artifactNames, '') }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Build Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + downloadType: specific files + itemPattern: "**" + downloadPath: $(Build.ArtifactStagingDirectory)\artifacts + checkDownloadedFiles: true + - powershell: eng/common/sdl/extract-artifact-packages.ps1 + -InputPath $(Build.ArtifactStagingDirectory)\artifacts\BlobArtifacts + -ExtractPath $(Build.ArtifactStagingDirectory)\artifacts\BlobArtifacts + displayName: Extract Blob Artifacts + continueOnError: ${{ parameters.sdlContinueOnError }} + - powershell: eng/common/sdl/extract-artifact-packages.ps1 + -InputPath $(Build.ArtifactStagingDirectory)\artifacts\PackageArtifacts + -ExtractPath $(Build.ArtifactStagingDirectory)\artifacts\PackageArtifacts + displayName: Extract Package Artifacts + continueOnError: ${{ parameters.sdlContinueOnError }} + - task: NuGetToolInstaller@1 + displayName: 'Install NuGet.exe' + - task: NuGetCommand@2 + displayName: 'Install Guardian' + inputs: + restoreSolution: $(Build.SourcesDirectory)\eng\common\sdl\packages.config + feedsToUse: config + nugetConfigPath: $(Build.SourcesDirectory)\eng\common\sdl\NuGet.config + externalFeedCredentials: GuardianConnect + restoreDirectory: $(Build.SourcesDirectory)\.packages + - ${{ if ne(parameters.overrideParameters, '') }}: + - powershell: eng/common/sdl/execute-all-sdl-tools.ps1 ${{ parameters.overrideParameters }} + displayName: Execute SDL + continueOnError: ${{ parameters.sdlContinueOnError }} + - ${{ if eq(parameters.overrideParameters, '') }}: + - powershell: eng/common/sdl/execute-all-sdl-tools.ps1 + -GuardianPackageName Microsoft.Guardian.Cli.0.53.3 + -NugetPackageDirectory $(Build.SourcesDirectory)\.packages + -AzureDevOpsAccessToken $(dn-bot-dotnet-build-rw-code-rw) + ${{ parameters.additionalParameters }} + displayName: Execute SDL + continueOnError: ${{ parameters.sdlContinueOnError }} diff --git a/eng/common/templates/job/generate-graph-files.yml b/eng/common/templates/job/generate-graph-files.yml new file mode 100644 index 000000000..e54ce956f --- /dev/null +++ b/eng/common/templates/job/generate-graph-files.yml @@ -0,0 +1,48 @@ +parameters: + # Optional: dependencies of the job + dependsOn: '' + + # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool + pool: {} + + # Optional: Include toolset dependencies in the generated graph files + includeToolset: false + +jobs: +- job: Generate_Graph_Files + + dependsOn: ${{ parameters.dependsOn }} + + displayName: Generate Graph Files + + pool: ${{ parameters.pool }} + + variables: + # Publish-Build-Assets provides: MaestroAccessToken, BotAccount-dotnet-maestro-bot-PAT + # DotNet-AllOrgs-Darc-Pats provides: dn-bot-devdiv-dnceng-rw-code-pat + - group: Publish-Build-Assets + - group: DotNet-AllOrgs-Darc-Pats + - name: _GraphArguments + value: -gitHubPat $(BotAccount-dotnet-maestro-bot-PAT) + -azdoPat $(dn-bot-devdiv-dnceng-rw-code-pat) + -barToken $(MaestroAccessToken) + -outputFolder '$(Build.StagingDirectory)/GraphFiles/' + - ${{ if ne(parameters.includeToolset, 'false') }}: + - name: _GraphArguments + value: ${{ variables._GraphArguments }} -includeToolset + + steps: + - task: PowerShell@2 + displayName: Generate Graph Files + inputs: + filePath: eng\common\generate-graph-files.ps1 + arguments: $(_GraphArguments) + continueOnError: true + - task: PublishBuildArtifacts@1 + displayName: Publish Graph to Artifacts + inputs: + PathtoPublish: '$(Build.StagingDirectory)/GraphFiles' + PublishLocation: Container + ArtifactName: GraphFiles + continueOnError: true + condition: always() diff --git a/eng/common/templates/job/job.yml b/eng/common/templates/job/job.yml new file mode 100644 index 000000000..866967934 --- /dev/null +++ b/eng/common/templates/job/job.yml @@ -0,0 +1,244 @@ +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + +parameters: +# Job schema parameters - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + cancelTimeoutInMinutes: '' + condition: '' + container: '' + continueOnError: false + dependsOn: '' + displayName: '' + pool: '' + steps: [] + strategy: '' + timeoutInMinutes: '' + variables: [] + workspace: '' + +# Job base template specific parameters + # See schema documentation - https://github.com/dotnet/arcade/blob/master/Documentation/AzureDevOps/TemplateSchema.md + artifacts: '' + enableMicrobuild: false + enablePublishBuildArtifacts: false + enablePublishBuildAssets: false + enablePublishTestResults: false + enablePublishUsingPipelines: false + mergeTestResults: false + testRunTitle: '' + testResultsFormat: '' + name: '' + preSteps: [] + runAsPublic: false + +jobs: +- job: ${{ parameters.name }} + + ${{ if ne(parameters.cancelTimeoutInMinutes, '') }}: + cancelTimeoutInMinutes: ${{ parameters.cancelTimeoutInMinutes }} + + ${{ if ne(parameters.condition, '') }}: + condition: ${{ parameters.condition }} + + ${{ if ne(parameters.container, '') }}: + container: ${{ parameters.container }} + + ${{ if ne(parameters.continueOnError, '') }}: + continueOnError: ${{ parameters.continueOnError }} + + ${{ if ne(parameters.dependsOn, '') }}: + dependsOn: ${{ parameters.dependsOn }} + + ${{ if ne(parameters.displayName, '') }}: + displayName: ${{ parameters.displayName }} + + ${{ if ne(parameters.pool, '') }}: + pool: ${{ parameters.pool }} + + ${{ if ne(parameters.strategy, '') }}: + strategy: ${{ parameters.strategy }} + + ${{ if ne(parameters.timeoutInMinutes, '') }}: + timeoutInMinutes: ${{ parameters.timeoutInMinutes }} + + variables: + - ${{ if ne(parameters.enableTelemetry, 'false') }}: + - name: DOTNET_CLI_TELEMETRY_PROFILE + value: '$(Build.Repository.Uri)' + - ${{ if eq(parameters.enableRichCodeNavigation, 'true') }}: + - name: EnableRichCodeNavigation + value: 'true' + - ${{ each variable in parameters.variables }}: + # handle name-value variable syntax + # example: + # - name: [key] + # value: [value] + - ${{ if ne(variable.name, '') }}: + - name: ${{ variable.name }} + value: ${{ variable.value }} + + # handle variable groups + - ${{ if ne(variable.group, '') }}: + - group: ${{ variable.group }} + + # handle key-value variable syntax. + # example: + # - [key]: [value] + - ${{ if and(eq(variable.name, ''), eq(variable.group, '')) }}: + - ${{ each pair in variable }}: + - name: ${{ pair.key }} + value: ${{ pair.value }} + + # DotNet-HelixApi-Access provides 'HelixApiAccessToken' for internal builds + - ${{ if and(eq(parameters.enableTelemetry, 'true'), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: DotNet-HelixApi-Access + + ${{ if ne(parameters.workspace, '') }}: + workspace: ${{ parameters.workspace }} + + steps: + - ${{ if ne(parameters.preSteps, '') }}: + - ${{ each preStep in parameters.preSteps }}: + - ${{ preStep }} + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + - task: MicroBuildSigningPlugin@2 + displayName: Install MicroBuild plugin + inputs: + signType: $(_SignType) + zipSources: false + feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json + env: + TeamName: $(_TeamName) + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + + - task: NuGetAuthenticate@0 + + - ${{ if or(eq(parameters.artifacts.download, 'true'), ne(parameters.artifacts.download, '')) }}: + - task: DownloadPipelineArtifact@2 + inputs: + buildType: current + artifactName: ${{ coalesce(parameters.artifacts.download.name, 'Artifacts_$(Agent.OS)_$(_BuildConfig)') }} + targetPath: ${{ coalesce(parameters.artifacts.download.path, 'artifacts') }} + itemPattern: ${{ coalesce(parameters.artifacts.download.pattern, '**') }} + + - ${{ each step in parameters.steps }}: + - ${{ step }} + + - ${{ if eq(parameters.enableRichCodeNavigation, true) }}: + - task: RichCodeNavIndexer@0 + displayName: RichCodeNav Upload + inputs: + languages: ${{ coalesce(parameters.richCodeNavigationLanguage, 'csharp') }} + environment: ${{ coalesce(parameters.richCodeNavigationEnvironment, 'production') }} + richNavLogOutputDirectory: $(Build.SourcesDirectory)/artifacts/bin + continueOnError: true + + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: MicroBuildCleanup@1 + displayName: Execute Microbuild cleanup tasks + condition: and(always(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} + env: + TeamName: $(_TeamName) + + - ${{ if ne(parameters.artifacts.publish, '') }}: + - ${{ if or(eq(parameters.artifacts.publish.artifacts, 'true'), ne(parameters.artifacts.publish.artifacts, '')) }}: + - task: CopyFiles@2 + displayName: Gather binaries for publish to artifacts + inputs: + SourceFolder: 'artifacts/bin' + Contents: '**' + TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/bin' + - task: CopyFiles@2 + displayName: Gather packages for publish to artifacts + inputs: + SourceFolder: 'artifacts/packages' + Contents: '**' + TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/packages' + - task: PublishBuildArtifacts@1 + displayName: Publish pipeline artifacts + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/artifacts' + PublishLocation: Container + ArtifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }} + continueOnError: true + condition: always() + - ${{ if or(eq(parameters.artifacts.publish.logs, 'true'), ne(parameters.artifacts.publish.logs, '')) }}: + - publish: artifacts/log + artifact: ${{ coalesce(parameters.artifacts.publish.logs.name, 'Logs_Build_$(Agent.Os)_$(_BuildConfig)') }} + displayName: Publish logs + continueOnError: true + condition: always() + - ${{ if or(eq(parameters.artifacts.publish.manifests, 'true'), ne(parameters.artifacts.publish.manifests, '')) }}: + - ${{ if and(ne(parameters.enablePublishUsingPipelines, 'true'), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: CopyFiles@2 + displayName: Gather Asset Manifests + inputs: + SourceFolder: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/AssetManifest' + TargetFolder: '$(Build.ArtifactStagingDirectory)/AssetManifests' + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), eq(variables['_DotNetPublishToBlobFeed'], 'true')) + + - task: PublishBuildArtifacts@1 + displayName: Push Asset Manifests + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/AssetManifests' + PublishLocation: Container + ArtifactName: AssetManifests + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), eq(variables['_DotNetPublishToBlobFeed'], 'true')) + + - ${{ if ne(parameters.enablePublishBuildArtifacts, 'false') }}: + - task: PublishBuildArtifacts@1 + displayName: Publish Logs + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)' + PublishLocation: Container + ArtifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)' ) }} + continueOnError: true + condition: always() + + - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'xunit')) }}: + - task: PublishTestResults@2 + displayName: Publish XUnit Test Results + inputs: + testResultsFormat: 'xUnit' + testResultsFiles: '*.xml' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' + testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-xunit + mergeTestResults: ${{ parameters.mergeTestResults }} + continueOnError: true + condition: always() + - ${{ if or(and(eq(parameters.enablePublishTestResults, 'true'), eq(parameters.testResultsFormat, '')), eq(parameters.testResultsFormat, 'vstest')) }}: + - task: PublishTestResults@2 + displayName: Publish TRX Test Results + inputs: + testResultsFormat: 'VSTest' + testResultsFiles: '*.trx' + searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' + testRunTitle: ${{ coalesce(parameters.testRunTitle, parameters.name, '$(System.JobName)') }}-trx + mergeTestResults: ${{ parameters.mergeTestResults }} + continueOnError: true + condition: always() + + - ${{ if and(eq(parameters.enablePublishBuildAssets, true), ne(parameters.enablePublishUsingPipelines, 'true'), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: CopyFiles@2 + displayName: Gather Asset Manifests + inputs: + SourceFolder: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/AssetManifest' + TargetFolder: '$(Build.StagingDirectory)/AssetManifests' + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), eq(variables['_DotNetPublishToBlobFeed'], 'true')) + + - task: PublishBuildArtifacts@1 + displayName: Push Asset Manifests + inputs: + PathtoPublish: '$(Build.StagingDirectory)/AssetManifests' + PublishLocation: Container + ArtifactName: AssetManifests + continueOnError: ${{ parameters.continueOnError }} + condition: and(succeeded(), eq(variables['_DotNetPublishToBlobFeed'], 'true')) diff --git a/eng/common/templates/job/onelocbuild.yml b/eng/common/templates/job/onelocbuild.yml new file mode 100644 index 000000000..2acdd5256 --- /dev/null +++ b/eng/common/templates/job/onelocbuild.yml @@ -0,0 +1,85 @@ +parameters: + # Optional: dependencies of the job + dependsOn: '' + + # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool + pool: + vmImage: vs2017-win2016 + + CeapexPat: $(dn-bot-ceapex-package-r) # PAT for the loc AzDO instance https://dev.azure.com/ceapex + GithubPat: $(BotAccount-dotnet-bot-repo-PAT) + + SourcesDirectory: $(Build.SourcesDirectory) + CreatePr: true + AutoCompletePr: false + UseLfLineEndings: true + UseCheckedInLocProjectJson: false + LanguageSet: VS_Main_Languages + LclSource: lclFilesInRepo + LclPackageId: '' + RepoType: gitHub + condition: '' + +jobs: +- job: OneLocBuild + + dependsOn: ${{ parameters.dependsOn }} + + displayName: OneLocBuild + + pool: ${{ parameters.pool }} + + variables: + - group: OneLocBuildVariables # Contains the CeapexPat and GithubPat + - name: _GenerateLocProjectArguments + value: -SourcesDirectory ${{ parameters.SourcesDirectory }} + -LanguageSet "${{ parameters.LanguageSet }}" + -CreateNeutralXlfs + - ${{ if eq(parameters.UseCheckedInLocProjectJson, 'true') }}: + - name: _GenerateLocProjectArguments + value: ${{ variables._GenerateLocProjectArguments }} -UseCheckedInLocProjectJson + + + steps: + - task: Powershell@2 + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/generate-locproject.ps1 + arguments: $(_GenerateLocProjectArguments) + displayName: Generate LocProject.json + condition: ${{ parameters.condition }} + + - task: OneLocBuild@2 + displayName: OneLocBuild + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + inputs: + locProj: eng/Localize/LocProject.json + outDir: $(Build.ArtifactStagingDirectory) + lclSource: ${{ parameters.LclSource }} + lclPackageId: ${{ parameters.LclPackageId }} + isCreatePrSelected: ${{ parameters.CreatePr }} + ${{ if eq(parameters.CreatePr, true) }}: + isAutoCompletePrSelected: ${{ parameters.AutoCompletePr }} + isUseLfLineEndingsSelected: ${{ parameters.UseLfLineEndings }} + packageSourceAuth: patAuth + patVariable: ${{ parameters.CeapexPat }} + ${{ if eq(parameters.RepoType, 'gitHub') }}: + repoType: ${{ parameters.RepoType }} + gitHubPatVariable: "${{ parameters.GithubPat }}" + condition: ${{ parameters.condition }} + + - task: PublishBuildArtifacts@1 + displayName: Publish Localization Files + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/loc' + PublishLocation: Container + ArtifactName: Loc + condition: ${{ parameters.condition }} + + - task: PublishBuildArtifacts@1 + displayName: Publish LocProject.json + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/eng/Localize/' + PublishLocation: Container + ArtifactName: Loc + condition: ${{ parameters.condition }} \ No newline at end of file diff --git a/eng/common/templates/job/publish-build-assets.yml b/eng/common/templates/job/publish-build-assets.yml new file mode 100644 index 000000000..3b9e2524f --- /dev/null +++ b/eng/common/templates/job/publish-build-assets.yml @@ -0,0 +1,101 @@ +parameters: + configuration: 'Debug' + + # Optional: condition for the job to run + condition: '' + + # Optional: 'true' if future jobs should run even if this job fails + continueOnError: false + + # Optional: dependencies of the job + dependsOn: '' + + # Optional: Include PublishBuildArtifacts task + enablePublishBuildArtifacts: false + + # Optional: A defined YAML pool - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#pool + pool: {} + + # Optional: should run as a public build even in the internal project + # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + runAsPublic: false + + # Optional: whether the build's artifacts will be published using release pipelines or direct feed publishing + publishUsingPipelines: false + +jobs: +- job: Asset_Registry_Publish + + dependsOn: ${{ parameters.dependsOn }} + + displayName: Publish to Build Asset Registry + + pool: ${{ parameters.pool }} + + variables: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - name: _BuildConfig + value: ${{ parameters.configuration }} + - group: Publish-Build-Assets + - group: AzureDevOps-Artifact-Feeds-Pats + # Skip component governance and codesign validation for SDL. These jobs + # create no content. + - name: skipComponentGovernanceDetection + value: true + - name: runCodesignValidationInjection + value: false + + steps: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download artifact + inputs: + artifactName: AssetManifests + downloadPath: '$(Build.StagingDirectory)/Download' + checkDownloadedFiles: true + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: NuGetAuthenticate@0 + + - task: PowerShell@2 + displayName: Enable cross-org NuGet feed authentication + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/enable-cross-org-publishing.ps1 + arguments: -token $(dn-bot-all-orgs-artifact-feeds-rw) + + - task: PowerShell@2 + displayName: Publish Build Assets + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task PublishBuildAssets -restore -msbuildEngine dotnet + /p:ManifestsPath='$(Build.StagingDirectory)/Download/AssetManifests' + /p:BuildAssetRegistryToken=$(MaestroAccessToken) + /p:MaestroApiEndpoint=https://maestro-prod.westus2.cloudapp.azure.com + /p:PublishUsingPipelines=${{ parameters.publishUsingPipelines }} + /p:Configuration=$(_BuildConfig) + /p:OfficialBuildId=$(Build.BuildNumber) + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + + - task: powershell@2 + displayName: Create ReleaseConfigs Artifact + inputs: + targetType: inline + script: | + Add-Content -Path "$(Build.StagingDirectory)/ReleaseConfigs.txt" -Value $(BARBuildId) + Add-Content -Path "$(Build.StagingDirectory)/ReleaseConfigs.txt" -Value "$(DefaultChannels)" + Add-Content -Path "$(Build.StagingDirectory)/ReleaseConfigs.txt" -Value $(IsStableBuild) + + - task: PublishBuildArtifacts@1 + displayName: Publish ReleaseConfigs Artifact + inputs: + PathtoPublish: '$(Build.StagingDirectory)/ReleaseConfigs.txt' + PublishLocation: Container + ArtifactName: ReleaseConfigs + + - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: + - template: /eng/common/templates/steps/publish-logs.yml + parameters: + JobLabel: 'Publish_Artifacts_Logs' diff --git a/eng/common/templates/job/source-build.yml b/eng/common/templates/job/source-build.yml new file mode 100644 index 000000000..5023d36dc --- /dev/null +++ b/eng/common/templates/job/source-build.yml @@ -0,0 +1,60 @@ +parameters: + # This template adds arcade-powered source-build to CI. The template produces a server job with a + # default ID 'Source_Build_Complete' to put in a dependency list if necessary. + + # Specifies the prefix for source-build jobs added to pipeline. Use this if disambiguation needed. + jobNamePrefix: 'Source_Build' + + # Defines the platform on which to run the job. By default, a linux-x64 machine, suitable for + # managed-only repositories. This is an object with these properties: + # + # name: '' + # The name of the job. This is included in the job ID. + # targetRID: '' + # The name of the target RID to use, instead of the one auto-detected by Arcade. + # nonPortable: false + # Enables non-portable mode. This means a more specific RID (e.g. fedora.32-x64 rather than + # linux-x64), and compiling against distro-provided packages rather than portable ones. + # skipPublishValidation: false + # Disables publishing validation. By default, a check is performed to ensure no packages are + # published by source-build. + # container: '' + # A container to use. Runs in docker. + # pool: {} + # A pool to use. Runs directly on an agent. + # buildScript: '' + # Specifies the build script to invoke to perform the build in the repo. The default + # './build.sh' should work for typical Arcade repositories, but this is customizable for + # difficult situations. + # jobProperties: {} + # A list of job properties to inject at the top level, for potential extensibility beyond + # container and pool. + platform: {} + + # The default VM host AzDO pool. This should be capable of running Docker containers: almost all + # source-build builds run in Docker, including the default managed platform. + defaultContainerHostPool: + vmImage: ubuntu-20.04 + +jobs: +- job: ${{ parameters.jobNamePrefix }}_${{ parameters.platform.name }} + displayName: Source-Build (${{ parameters.platform.name }}) + + ${{ each property in parameters.platform.jobProperties }}: + ${{ property.key }}: ${{ property.value }} + + ${{ if ne(parameters.platform.container, '') }}: + container: ${{ parameters.platform.container }} + + ${{ if eq(parameters.platform.pool, '') }}: + pool: ${{ parameters.defaultContainerHostPool }} + ${{ if ne(parameters.platform.pool, '') }}: + pool: ${{ parameters.platform.pool }} + + workspace: + clean: all + + steps: + - template: /eng/common/templates/steps/source-build.yml + parameters: + platform: ${{ parameters.platform }} diff --git a/eng/common/templates/job/source-index-stage1.yml b/eng/common/templates/job/source-index-stage1.yml new file mode 100644 index 000000000..a649d2b59 --- /dev/null +++ b/eng/common/templates/job/source-index-stage1.yml @@ -0,0 +1,58 @@ +parameters: + runAsPublic: false + sourceIndexPackageVersion: 1.0.1-20210421.1 + sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json + sourceIndexBuildCommand: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "eng/common/build.ps1 -restore -build -binarylog -ci" + preSteps: [] + binlogPath: artifacts/log/Debug/Build.binlog + pool: + vmImage: vs2017-win2016 + +jobs: +- job: SourceIndexStage1 + variables: + - name: SourceIndexPackageVersion + value: ${{ parameters.sourceIndexPackageVersion }} + - name: SourceIndexPackageSource + value: ${{ parameters.sourceIndexPackageSource }} + - name: BinlogPath + value: ${{ parameters.binlogPath }} + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - group: source-dot-net stage1 variables + + pool: ${{ parameters.pool }} + steps: + - ${{ each preStep in parameters.preSteps }}: + - ${{ preStep }} + + - task: UseDotNet@2 + displayName: Use .NET Core sdk 3.1 + inputs: + packageType: sdk + version: 3.1.x + + - task: UseDotNet@2 + displayName: Use .NET Core sdk + inputs: + useGlobalJson: true + + - script: | + dotnet tool install BinLogToSln --version $(SourceIndexPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path .source-index/tools + dotnet tool install UploadIndexStage1 --version $(SourceIndexPackageVersion) --add-source $(SourceIndexPackageSource) --tool-path .source-index/tools + echo ##vso[task.prependpath]$(Build.SourcesDirectory)/.source-index/tools + displayName: Download Tools + + - script: ${{ parameters.sourceIndexBuildCommand }} + displayName: Build Repository + + - script: BinLogToSln -i $(BinlogPath) -r $(Build.SourcesDirectory) -n $(Build.Repository.Name) -o .source-index/stage1output + displayName: Process Binlog into indexable sln + env: + DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX: 2 + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - script: UploadIndexStage1 -i .source-index/stage1output -n $(Build.Repository.Name) + displayName: Upload stage1 artifacts to source index + env: + BLOB_CONTAINER_URL: $(source-dot-net-stage1-blob-container-url) + DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX: 2 diff --git a/eng/common/templates/jobs/jobs.yml b/eng/common/templates/jobs/jobs.yml new file mode 100644 index 000000000..a1f8fce96 --- /dev/null +++ b/eng/common/templates/jobs/jobs.yml @@ -0,0 +1,99 @@ +parameters: + # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md + continueOnError: false + + # Optional: Include PublishBuildArtifacts task + enablePublishBuildArtifacts: false + + # Optional: Enable publishing using release pipelines + enablePublishUsingPipelines: false + + # Optional: Enable running the source-build jobs to build repo from source + enableSourceBuild: false + + # Optional: Parameters for source-build template. + # See /eng/common/templates/jobs/source-build.yml for options + sourceBuildParameters: [] + + graphFileGeneration: + # Optional: Enable generating the graph files at the end of the build + enabled: false + # Optional: Include toolset dependencies in the generated graph files + includeToolset: false + + # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job + jobs: [] + + # Optional: Override automatically derived dependsOn value for "publish build assets" job + publishBuildAssetsDependsOn: '' + + # Optional: should run as a public build even in the internal project + # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + runAsPublic: false + + enableSourceIndex: false + sourceIndexParams: {} + +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + +jobs: +- ${{ each job in parameters.jobs }}: + - template: ../job/job.yml + parameters: + # pass along parameters + ${{ each parameter in parameters }}: + ${{ if ne(parameter.key, 'jobs') }}: + ${{ parameter.key }}: ${{ parameter.value }} + + # pass along job properties + ${{ each property in job }}: + ${{ if ne(property.key, 'job') }}: + ${{ property.key }}: ${{ property.value }} + + name: ${{ job.job }} + +- ${{ if eq(parameters.enableSourceBuild, true) }}: + - template: /eng/common/templates/jobs/source-build.yml + parameters: + allCompletedJobId: Source_Build_Complete + ${{ each parameter in parameters.sourceBuildParameters }}: + ${{ parameter.key }}: ${{ parameter.value }} + +- ${{ if eq(parameters.enableSourceIndex, 'true') }}: + - template: ../job/source-index-stage1.yml + parameters: + runAsPublic: ${{ parameters.runAsPublic }} + ${{ each parameter in parameters.sourceIndexParams }}: + ${{ parameter.key }}: ${{ parameter.value }} + +- ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + + - ${{ if or(eq(parameters.enablePublishBuildAssets, true), eq(parameters.artifacts.publish.manifests, 'true'), ne(parameters.artifacts.publish.manifests, '')) }}: + - template: ../job/publish-build-assets.yml + parameters: + continueOnError: ${{ parameters.continueOnError }} + dependsOn: + - ${{ if ne(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.publishBuildAssetsDependsOn }}: + - ${{ job.job }} + - ${{ if eq(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.jobs }}: + - ${{ job.job }} + - ${{ if eq(parameters.enableSourceBuild, true) }}: + - Source_Build_Complete + pool: + vmImage: vs2017-win2016 + runAsPublic: ${{ parameters.runAsPublic }} + publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} + enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} + + - ${{ if eq(parameters.graphFileGeneration.enabled, true) }}: + - template: ../job/generate-graph-files.yml + parameters: + continueOnError: ${{ parameters.continueOnError }} + includeToolset: ${{ parameters.graphFileGeneration.includeToolset }} + dependsOn: + - Asset_Registry_Publish + pool: + vmImage: vs2017-win2016 diff --git a/eng/common/templates/jobs/source-build.yml b/eng/common/templates/jobs/source-build.yml new file mode 100644 index 000000000..00aa98eb3 --- /dev/null +++ b/eng/common/templates/jobs/source-build.yml @@ -0,0 +1,46 @@ +parameters: + # This template adds arcade-powered source-build to CI. A job is created for each platform, as + # well as an optional server job that completes when all platform jobs complete. + + # The name of the "join" job for all source-build platforms. If set to empty string, the job is + # not included. Existing repo pipelines can use this job depend on all source-build jobs + # completing without maintaining a separate list of every single job ID: just depend on this one + # server job. By default, not included. Recommended name if used: 'Source_Build_Complete'. + allCompletedJobId: '' + + # See /eng/common/templates/job/source-build.yml + jobNamePrefix: 'Source_Build' + + # This is the default platform provided by Arcade, intended for use by a managed-only repo. + defaultManagedPlatform: + name: 'Managed' + container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-7-3e800f1-20190501005343' + + # Defines the platforms on which to run build jobs. One job is created for each platform, and the + # object in this array is sent to the job template as 'platform'. If no platforms are specified, + # one job runs on 'defaultManagedPlatform'. + platforms: [] + +jobs: + +- ${{ if ne(parameters.allCompletedJobId, '') }}: + - job: ${{ parameters.allCompletedJobId }} + displayName: Source-Build Complete + pool: server + dependsOn: + - ${{ each platform in parameters.platforms }}: + - ${{ parameters.jobNamePrefix }}_${{ platform.name }} + - ${{ if eq(length(parameters.platforms), 0) }}: + - ${{ parameters.jobNamePrefix }}_${{ parameters.defaultManagedPlatform.name }} + +- ${{ each platform in parameters.platforms }}: + - template: /eng/common/templates/job/source-build.yml + parameters: + jobNamePrefix: ${{ parameters.jobNamePrefix }} + platform: ${{ platform }} + +- ${{ if eq(length(parameters.platforms), 0) }}: + - template: /eng/common/templates/job/source-build.yml + parameters: + jobNamePrefix: ${{ parameters.jobNamePrefix }} + platform: ${{ parameters.defaultManagedPlatform }} diff --git a/eng/common/templates/phases/base.yml b/eng/common/templates/phases/base.yml new file mode 100644 index 000000000..0123cf43b --- /dev/null +++ b/eng/common/templates/phases/base.yml @@ -0,0 +1,130 @@ +parameters: + # Optional: Clean sources before building + clean: true + + # Optional: Git fetch depth + fetchDepth: '' + + # Optional: name of the phase (not specifying phase name may cause name collisions) + name: '' + # Optional: display name of the phase + displayName: '' + + # Optional: condition for the job to run + condition: '' + + # Optional: dependencies of the phase + dependsOn: '' + + # Required: A defined YAML queue + queue: {} + + # Required: build steps + steps: [] + + # Optional: variables + variables: {} + + # Optional: should run as a public build even in the internal project + # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. + runAsPublic: false + + ## Telemetry variables + + # Optional: enable sending telemetry + # if 'true', these "variables" must be specified in the variables object or as part of the queue matrix + # _HelixBuildConfig - differentiate between Debug, Release, other + # _HelixSource - Example: build/product + # _HelixType - Example: official/dotnet/arcade/$(Build.SourceBranch) + enableTelemetry: false + + # Optional: Enable installing Microbuild plugin + # if 'true', these "variables" must be specified in the variables object or as part of the queue matrix + # _TeamName - the name of your team + # _SignType - 'test' or 'real' + enableMicrobuild: false + +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + +phases: +- phase: ${{ parameters.name }} + + ${{ if ne(parameters.displayName, '') }}: + displayName: ${{ parameters.displayName }} + + ${{ if ne(parameters.condition, '') }}: + condition: ${{ parameters.condition }} + + ${{ if ne(parameters.dependsOn, '') }}: + dependsOn: ${{ parameters.dependsOn }} + + queue: ${{ parameters.queue }} + + ${{ if ne(parameters.variables, '') }}: + variables: + ${{ insert }}: ${{ parameters.variables }} + + steps: + - checkout: self + clean: ${{ parameters.clean }} + ${{ if ne(parameters.fetchDepth, '') }}: + fetchDepth: ${{ parameters.fetchDepth }} + + - ${{ if eq(parameters.enableTelemetry, 'true') }}: + - template: /eng/common/templates/steps/telemetry-start.yml + parameters: + buildConfig: $(_HelixBuildConfig) + helixSource: $(_HelixSource) + helixType: $(_HelixType) + runAsPublic: ${{ parameters.runAsPublic }} + + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + # Internal only resource, and Microbuild signing shouldn't be applied to PRs. + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: MicroBuildSigningPlugin@2 + displayName: Install MicroBuild plugin + inputs: + signType: $(_SignType) + zipSources: false + feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json + + env: + TeamName: $(_TeamName) + continueOnError: false + condition: and(succeeded(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + + # Run provided build steps + - ${{ parameters.steps }} + + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + # Internal only resources + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: MicroBuildCleanup@1 + displayName: Execute Microbuild cleanup tasks + condition: and(always(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + env: + TeamName: $(_TeamName) + + - ${{ if eq(parameters.enableTelemetry, 'true') }}: + - template: /eng/common/templates/steps/telemetry-end.yml + parameters: + helixSource: $(_HelixSource) + helixType: $(_HelixType) + + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: CopyFiles@2 + displayName: Gather Asset Manifests + inputs: + SourceFolder: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/AssetManifest' + TargetFolder: '$(Build.StagingDirectory)/AssetManifests' + continueOnError: false + condition: and(succeeded(), eq(variables['_DotNetPublishToBlobFeed'], 'true')) + - task: PublishBuildArtifacts@1 + displayName: Push Asset Manifests + inputs: + PathtoPublish: '$(Build.StagingDirectory)/AssetManifests' + PublishLocation: Container + ArtifactName: AssetManifests + continueOnError: false + condition: and(succeeded(), eq(variables['_DotNetPublishToBlobFeed'], 'true')) diff --git a/eng/common/templates/phases/publish-build-assets.yml b/eng/common/templates/phases/publish-build-assets.yml new file mode 100644 index 000000000..4e51e472e --- /dev/null +++ b/eng/common/templates/phases/publish-build-assets.yml @@ -0,0 +1,52 @@ +parameters: + dependsOn: '' + queue: {} + configuration: 'Debug' + condition: succeeded() + continueOnError: false + runAsPublic: false + publishUsingPipelines: false +phases: + - phase: Asset_Registry_Publish + displayName: Publish to Build Asset Registry + dependsOn: ${{ parameters.dependsOn }} + queue: ${{ parameters.queue }} + variables: + _BuildConfig: ${{ parameters.configuration }} + steps: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download artifact + inputs: + artifactName: AssetManifests + downloadPath: '$(Build.StagingDirectory)/Download' + checkDownloadedFiles: true + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + - task: AzureKeyVault@1 + inputs: + azureSubscription: 'DotNet-Engineering-Services_KeyVault' + KeyVaultName: EngKeyVault + SecretsFilter: 'MaestroAccessToken' + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + - task: PowerShell@2 + displayName: Publish Build Assets + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task PublishBuildAssets -restore -msbuildEngine dotnet + /p:ManifestsPath='$(Build.StagingDirectory)/Download/AssetManifests' + /p:BuildAssetRegistryToken=$(MaestroAccessToken) + /p:MaestroApiEndpoint=https://maestro-prod.westus2.cloudapp.azure.com + /p:PublishUsingPipelines=${{ parameters.publishUsingPipelines }} + /p:Configuration=$(_BuildConfig) + condition: ${{ parameters.condition }} + continueOnError: ${{ parameters.continueOnError }} + - task: PublishBuildArtifacts@1 + displayName: Publish Logs to VSTS + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)' + PublishLocation: Container + ArtifactName: $(Agent.Os)_Asset_Registry_Publish + continueOnError: true + condition: always() diff --git a/eng/common/templates/post-build/channels/generic-internal-channel.yml b/eng/common/templates/post-build/channels/generic-internal-channel.yml new file mode 100644 index 000000000..8990dfc8c --- /dev/null +++ b/eng/common/templates/post-build/channels/generic-internal-channel.yml @@ -0,0 +1,190 @@ +parameters: + BARBuildId: '' + PromoteToChannelIds: '' + artifactsPublishingAdditionalParameters: '' + dependsOn: + - Validate + publishInstallersAndChecksums: true + symbolPublishingAdditionalParameters: '' + stageName: '' + channelName: '' + channelId: '' + transportFeed: '' + shippingFeed: '' + symbolsFeed: '' + +stages: +- stage: ${{ parameters.stageName }} + dependsOn: ${{ parameters.dependsOn }} + variables: + - template: ../common-variables.yml + displayName: ${{ parameters.channelName }} Publishing + jobs: + - template: ../setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - job: publish_symbols + displayName: Symbol Publishing + dependsOn: setupMaestroVars + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.TargetChannels'], format('[{0}]', ${{ parameters.channelId }} )) + variables: + - group: DotNet-Symbol-Server-Pats + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] + pool: + vmImage: 'windows-2019' + steps: + - script: echo "##vso[task.logissue type=warning]Going forward, v2 Arcade publishing is no longer supported. Please read https://github.com/dotnet/arcade/blob/main/Documentation/CorePackages/Publishing.md for details, then contact dnceng if you have further questions." + displayName: Warn about v2 Arcade Publishing Usage + + # This is necessary whenever we want to publish/restore to an AzDO private feed + - task: NuGetAuthenticate@0 + displayName: 'Authenticate to AzDO Feeds' + + - task: DownloadBuildArtifacts@0 + displayName: Download Build Assets + continueOnError: true + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + downloadType: 'specific' + itemPattern: | + PdbArtifacts/** + BlobArtifacts/** + downloadPath: '$(Build.ArtifactStagingDirectory)' + checkDownloadedFiles: true + + # This is necessary whenever we want to publish/restore to an AzDO private feed + # Since sdk-task.ps1 tries to restore packages we need to do this authentication here + # otherwise it'll complain about accessing a private feed. + - task: NuGetAuthenticate@0 + displayName: 'Authenticate to AzDO Feeds' + + - task: PowerShell@2 + displayName: Enable cross-org publishing + inputs: + filePath: eng\common\enable-cross-org-publishing.ps1 + arguments: -token $(dn-bot-dnceng-artifact-feeds-rw) + + - task: PowerShell@2 + displayName: Publish + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task PublishToSymbolServers -restore -msbuildEngine dotnet + /p:DotNetSymbolServerTokenMsdl=$(microsoft-symbol-server-pat) + /p:DotNetSymbolServerTokenSymWeb=$(symweb-symbol-server-pat) + /p:PDBArtifactsDirectory='$(Build.ArtifactStagingDirectory)/PDBArtifacts/' + /p:BlobBasePath='$(Build.ArtifactStagingDirectory)/BlobArtifacts/' + /p:SymbolPublishingExclusionsFile='$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt' + /p:Configuration=Release + /p:PublishToMSDL=false + ${{ parameters.symbolPublishingAdditionalParameters }} + + - template: ../../steps/publish-logs.yml + parameters: + StageLabel: '${{ parameters.stageName }}' + JobLabel: 'SymbolPublishing' + + - job: publish_assets + displayName: Publish Assets + dependsOn: setupMaestroVars + timeoutInMinutes: 120 + variables: + - name: BARBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] + - name: IsStableBuild + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.IsStableBuild'] ] + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.TargetChannels'], format('[{0}]', ${{ parameters.channelId }} )) + pool: + vmImage: 'windows-2019' + steps: + - script: echo "##vso[task.logissue type=warning]Going forward, v2 Arcade publishing is no longer supported. Please read https://github.com/dotnet/arcade/blob/main/Documentation/CorePackages/Publishing.md for details, then contact dnceng if you have further questions." + displayName: Warn about v2 Arcade Publishing Usage + + - task: DownloadBuildArtifacts@0 + displayName: Download Build Assets + continueOnError: true + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + downloadType: 'specific' + itemPattern: | + PackageArtifacts/** + BlobArtifacts/** + AssetManifests/** + downloadPath: '$(Build.ArtifactStagingDirectory)' + checkDownloadedFiles: true + + - task: NuGetToolInstaller@1 + displayName: 'Install NuGet.exe' + + # This is necessary whenever we want to publish/restore to an AzDO private feed + - task: NuGetAuthenticate@0 + displayName: 'Authenticate to AzDO Feeds' + + - task: PowerShell@2 + displayName: Enable cross-org publishing + inputs: + filePath: eng\common\enable-cross-org-publishing.ps1 + arguments: -token $(dn-bot-dnceng-artifact-feeds-rw) + + - task: PowerShell@2 + displayName: Publish Assets + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task PublishArtifactsInManifest -restore -msbuildEngine dotnet + /p:PublishingInfraVersion=2 + /p:IsStableBuild=$(IsStableBuild) + /p:IsInternalBuild=$(IsInternalBuild) + /p:RepositoryName=$(Build.Repository.Name) + /p:CommitSha=$(Build.SourceVersion) + /p:NugetPath=$(NuGetExeToolPath) + /p:AzdoTargetFeedPAT='$(dn-bot-dnceng-universal-packages-rw)' + /p:AzureStorageTargetFeedPAT='$(dotnetfeed-storage-access-key-1)' + /p:BARBuildId=$(BARBuildId) + /p:MaestroApiEndpoint='$(MaestroApiEndPoint)' + /p:BuildAssetRegistryToken='$(MaestroApiAccessToken)' + /p:ManifestsBasePath='$(Build.ArtifactStagingDirectory)/AssetManifests/' + /p:BlobBasePath='$(Build.ArtifactStagingDirectory)/BlobArtifacts/' + /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts/' + /p:Configuration=Release + /p:PublishInstallersAndChecksums=${{ parameters.publishInstallersAndChecksums }} + /p:ChecksumsTargetStaticFeed=$(InternalChecksumsBlobFeedUrl) + /p:ChecksumsAzureAccountKey=$(InternalChecksumsBlobFeedKey) + /p:InstallersTargetStaticFeed=$(InternalInstallersBlobFeedUrl) + /p:InstallersAzureAccountKey=$(InternalInstallersBlobFeedKey) + /p:AzureDevOpsStaticShippingFeed='${{ parameters.shippingFeed }}' + /p:AzureDevOpsStaticShippingFeedKey='$(dn-bot-dnceng-artifact-feeds-rw)' + /p:AzureDevOpsStaticTransportFeed='${{ parameters.transportFeed }}' + /p:AzureDevOpsStaticTransportFeedKey='$(dn-bot-dnceng-artifact-feeds-rw)' + /p:AzureDevOpsStaticSymbolsFeed='${{ parameters.symbolsFeed }}' + /p:AzureDevOpsStaticSymbolsFeedKey='$(dn-bot-dnceng-artifact-feeds-rw)' + /p:PublishToMSDL=false + ${{ parameters.artifactsPublishingAdditionalParameters }} + + - template: ../../steps/publish-logs.yml + parameters: + StageLabel: '${{ parameters.stageName }}' + JobLabel: 'AssetsPublishing' + + - template: ../../steps/add-build-to-channel.yml + parameters: + ChannelId: ${{ parameters.channelId }} diff --git a/eng/common/templates/post-build/channels/generic-public-channel.yml b/eng/common/templates/post-build/channels/generic-public-channel.yml new file mode 100644 index 000000000..3220c6a4f --- /dev/null +++ b/eng/common/templates/post-build/channels/generic-public-channel.yml @@ -0,0 +1,192 @@ +parameters: + BARBuildId: '' + PromoteToChannelIds: '' + artifactsPublishingAdditionalParameters: '' + dependsOn: + - Validate + publishInstallersAndChecksums: true + symbolPublishingAdditionalParameters: '' + stageName: '' + channelName: '' + channelId: '' + transportFeed: '' + shippingFeed: '' + symbolsFeed: '' + # If the channel name is empty, no links will be generated + akaMSChannelName: '' + +stages: +- stage: ${{ parameters.stageName }} + dependsOn: ${{ parameters.dependsOn }} + variables: + - template: ../common-variables.yml + displayName: ${{ parameters.channelName }} Publishing + jobs: + - template: ../setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - job: publish_symbols + displayName: Symbol Publishing + dependsOn: setupMaestroVars + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.TargetChannels'], format('[{0}]', ${{ parameters.channelId }} )) + variables: + - group: DotNet-Symbol-Server-Pats + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] + pool: + vmImage: 'windows-2019' + steps: + - script: echo "##vso[task.logissue type=warning]Going forward, v2 Arcade publishing is no longer supported. Please read https://github.com/dotnet/arcade/blob/main/Documentation/CorePackages/Publishing.md for details, then contact dnceng if you have further questions." + displayName: Warn about v2 Arcade Publishing Usage + + - task: DownloadBuildArtifacts@0 + displayName: Download Build Assets + continueOnError: true + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + downloadType: 'specific' + itemPattern: | + PdbArtifacts/** + BlobArtifacts/** + downloadPath: '$(Build.ArtifactStagingDirectory)' + checkDownloadedFiles: true + + # This is necessary whenever we want to publish/restore to an AzDO private feed + # Since sdk-task.ps1 tries to restore packages we need to do this authentication here + # otherwise it'll complain about accessing a private feed. + - task: NuGetAuthenticate@0 + displayName: 'Authenticate to AzDO Feeds' + + - task: PowerShell@2 + displayName: Enable cross-org publishing + inputs: + filePath: eng\common\enable-cross-org-publishing.ps1 + arguments: -token $(dn-bot-dnceng-artifact-feeds-rw) + + - task: PowerShell@2 + displayName: Publish + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task PublishToSymbolServers -restore -msbuildEngine dotnet + /p:DotNetSymbolServerTokenMsdl=$(microsoft-symbol-server-pat) + /p:DotNetSymbolServerTokenSymWeb=$(symweb-symbol-server-pat) + /p:PDBArtifactsDirectory='$(Build.ArtifactStagingDirectory)/PDBArtifacts/' + /p:BlobBasePath='$(Build.ArtifactStagingDirectory)/BlobArtifacts/' + /p:SymbolPublishingExclusionsFile='$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt' + /p:Configuration=Release + ${{ parameters.symbolPublishingAdditionalParameters }} + + - template: ../../steps/publish-logs.yml + parameters: + StageLabel: '${{ parameters.stageName }}' + JobLabel: 'SymbolPublishing' + + - job: publish_assets + displayName: Publish Assets + dependsOn: setupMaestroVars + timeoutInMinutes: 120 + variables: + - name: BARBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] + - name: IsStableBuild + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.IsStableBuild'] ] + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] + - name: ArtifactsCategory + value: ${{ coalesce(variables._DotNetArtifactsCategory, '.NETCore') }} + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.TargetChannels'], format('[{0}]', ${{ parameters.channelId }} )) + pool: + vmImage: 'windows-2019' + steps: + - script: echo "##vso[task.logissue type=warning]Going forward, v2 Arcade publishing is no longer supported. Please read https://github.com/dotnet/arcade/blob/main/Documentation/CorePackages/Publishing.md for details, then contact dnceng if you have further questions." + displayName: Warn about v2 Arcade Publishing Usage + + - task: DownloadBuildArtifacts@0 + displayName: Download Build Assets + continueOnError: true + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + downloadType: 'specific' + itemPattern: | + PackageArtifacts/** + BlobArtifacts/** + AssetManifests/** + downloadPath: '$(Build.ArtifactStagingDirectory)' + checkDownloadedFiles: true + + - task: NuGetToolInstaller@1 + displayName: 'Install NuGet.exe' + + # This is necessary whenever we want to publish/restore to an AzDO private feed + - task: NuGetAuthenticate@0 + displayName: 'Authenticate to AzDO Feeds' + + - task: PowerShell@2 + displayName: Enable cross-org publishing + inputs: + filePath: eng\common\enable-cross-org-publishing.ps1 + arguments: -token $(dn-bot-dnceng-artifact-feeds-rw) + + - task: PowerShell@2 + displayName: Publish Assets + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task PublishArtifactsInManifest -restore -msbuildEngine dotnet + /p:PublishingInfraVersion=2 + /p:ArtifactsCategory=$(ArtifactsCategory) + /p:IsStableBuild=$(IsStableBuild) + /p:IsInternalBuild=$(IsInternalBuild) + /p:RepositoryName=$(Build.Repository.Name) + /p:CommitSha=$(Build.SourceVersion) + /p:NugetPath=$(NuGetExeToolPath) + /p:AzdoTargetFeedPAT='$(dn-bot-dnceng-universal-packages-rw)' + /p:AzureStorageTargetFeedPAT='$(dotnetfeed-storage-access-key-1)' + /p:BARBuildId=$(BARBuildId) + /p:MaestroApiEndpoint='$(MaestroApiEndPoint)' + /p:BuildAssetRegistryToken='$(MaestroApiAccessToken)' + /p:ManifestsBasePath='$(Build.ArtifactStagingDirectory)/AssetManifests/' + /p:BlobBasePath='$(Build.ArtifactStagingDirectory)/BlobArtifacts/' + /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts/' + /p:Configuration=Release + /p:PublishInstallersAndChecksums=${{ parameters.publishInstallersAndChecksums }} + /p:InstallersTargetStaticFeed=$(InstallersBlobFeedUrl) + /p:InstallersAzureAccountKey=$(dotnetcli-storage-key) + /p:ChecksumsTargetStaticFeed=$(ChecksumsBlobFeedUrl) + /p:ChecksumsAzureAccountKey=$(dotnetclichecksums-storage-key) + /p:AzureDevOpsStaticShippingFeed='${{ parameters.shippingFeed }}' + /p:AzureDevOpsStaticShippingFeedKey='$(dn-bot-dnceng-artifact-feeds-rw)' + /p:AzureDevOpsStaticTransportFeed='${{ parameters.transportFeed }}' + /p:AzureDevOpsStaticTransportFeedKey='$(dn-bot-dnceng-artifact-feeds-rw)' + /p:AzureDevOpsStaticSymbolsFeed='${{ parameters.symbolsFeed }}' + /p:AzureDevOpsStaticSymbolsFeedKey='$(dn-bot-dnceng-artifact-feeds-rw)' + /p:LatestLinkShortUrlPrefix=dotnet/'${{ parameters.akaMSChannelName }}' + /p:AkaMSClientId=$(akams-client-id) + /p:AkaMSClientSecret=$(akams-client-secret) + ${{ parameters.artifactsPublishingAdditionalParameters }} + + - template: ../../steps/publish-logs.yml + parameters: + StageLabel: '${{ parameters.stageName }}' + JobLabel: 'AssetsPublishing' + + - template: ../../steps/add-build-to-channel.yml + parameters: + ChannelId: ${{ parameters.channelId }} diff --git a/eng/common/templates/post-build/common-variables.yml b/eng/common/templates/post-build/common-variables.yml new file mode 100644 index 000000000..c99fd7503 --- /dev/null +++ b/eng/common/templates/post-build/common-variables.yml @@ -0,0 +1,99 @@ +variables: + - group: AzureDevOps-Artifact-Feeds-Pats + - group: DotNet-Blob-Feed + - group: DotNet-DotNetCli-Storage + - group: DotNet-MSRC-Storage + - group: Publish-Build-Assets + + # .NET Core 3.1 Dev + - name: PublicDevRelease_31_Channel_Id + value: 128 + + # .NET 5 Dev + - name: Net_5_Dev_Channel_Id + value: 131 + + # .NET Eng - Validation + - name: Net_Eng_Validation_Channel_Id + value: 9 + + # .NET Eng - Latest + - name: Net_Eng_Latest_Channel_Id + value: 2 + + # .NET 3 Eng - Validation + - name: NET_3_Eng_Validation_Channel_Id + value: 390 + + # .NET 3 Eng + - name: NetCore_3_Tools_Channel_Id + value: 344 + + # .NET Core 3.0 Internal Servicing + - name: InternalServicing_30_Channel_Id + value: 184 + + # .NET Core 3.0 Release + - name: PublicRelease_30_Channel_Id + value: 19 + + # .NET Core 3.1 Release + - name: PublicRelease_31_Channel_Id + value: 129 + + # General Testing + - name: GeneralTesting_Channel_Id + value: 529 + + # .NET Core 3.1 Blazor Features + - name: NetCore_31_Blazor_Features_Channel_Id + value: 531 + + # .NET Core Experimental + - name: NetCore_Experimental_Channel_Id + value: 562 + + # Whether the build is internal or not + - name: IsInternalBuild + value: ${{ and(ne(variables['System.TeamProject'], 'public'), contains(variables['Build.SourceBranch'], 'internal')) }} + + # Default Maestro++ API Endpoint and API Version + - name: MaestroApiEndPoint + value: "https://maestro-prod.westus2.cloudapp.azure.com" + - name: MaestroApiAccessToken + value: $(MaestroAccessToken) + - name: MaestroApiVersion + value: "2020-02-20" + + - name: SourceLinkCLIVersion + value: 3.0.0 + - name: SymbolToolVersion + value: 1.0.1 + + # Feed Configurations + # These should include the suffix "/index.json" + + # Default locations for Installers and checksums + # Public Locations + - name: ChecksumsBlobFeedUrl + value: https://dotnetclichecksums.blob.core.windows.net/dotnet/index.json + - name: InstallersBlobFeedUrl + value: https://dotnetcli.blob.core.windows.net/dotnet/index.json + + # Private Locations + - name: InternalChecksumsBlobFeedUrl + value: https://dotnetclichecksumsmsrc.blob.core.windows.net/dotnet/index.json + - name: InternalChecksumsBlobFeedKey + value: $(dotnetclichecksumsmsrc-storage-key) + + - name: InternalInstallersBlobFeedUrl + value: https://dotnetclimsrc.blob.core.windows.net/dotnet/index.json + - name: InternalInstallersBlobFeedKey + value: $(dotnetclimsrc-access-key) + + # Skip component governance and codesign validation for SDL. These jobs + # create no content. + - name: skipComponentGovernanceDetection + value: true + - name: runCodesignValidationInjection + value: false diff --git a/eng/common/templates/post-build/post-build.yml b/eng/common/templates/post-build/post-build.yml new file mode 100644 index 000000000..4f79cf0f3 --- /dev/null +++ b/eng/common/templates/post-build/post-build.yml @@ -0,0 +1,589 @@ +parameters: + # Which publishing infra should be used. THIS SHOULD MATCH THE VERSION ON THE BUILD MANIFEST. + # Publishing V2 accepts optionally outlining the publishing stages - default is inline. + # Publishing V3 DOES NOT accept inlining the publishing stages. + publishingInfraVersion: 2 + # When set to true the publishing templates from the repo will be used + # otherwise Darc add-build-to-channel will be used to trigger the promotion pipeline + inline: true + + # Only used if inline==false. When set to true will stall the current build until + # the Promotion Pipeline build finishes. Otherwise, the current build will continue + # execution concurrently with the promotion build. + waitPublishingFinish: true + + BARBuildId: '' + PromoteToChannelIds: '' + + enableSourceLinkValidation: false + enableSigningValidation: true + enableSymbolValidation: false + enableNugetValidation: true + publishInstallersAndChecksums: true + SDLValidationParameters: + enable: false + continueOnError: false + params: '' + artifactNames: '' + downloadArtifacts: true + + # These parameters let the user customize the call to sdk-task.ps1 for publishing + # symbols & general artifacts as well as for signing validation + symbolPublishingAdditionalParameters: '' + artifactsPublishingAdditionalParameters: '' + signingValidationAdditionalParameters: '' + + # Which stages should finish execution before post-build stages start + validateDependsOn: + - build + publishDependsOn: + - Validate + + # Channel ID's instantiated in this file. + # When adding a new channel implementation the call to `check-channel-consistency.ps1` + # needs to be updated with the new channel ID + NetEngLatestChannelId: 2 + NetEngValidationChannelId: 9 + NetDev5ChannelId: 131 + NetDev6ChannelId: 1296 + GeneralTestingChannelId: 529 + NETCoreToolingDevChannelId: 548 + NETCoreToolingReleaseChannelId: 549 + NETInternalToolingChannelId: 551 + NETCoreExperimentalChannelId: 562 + NetEngServicesIntChannelId: 678 + NetEngServicesProdChannelId: 679 + NetCoreSDK313xxChannelId: 759 + NetCoreSDK313xxInternalChannelId: 760 + NetCoreSDK314xxChannelId: 921 + NetCoreSDK314xxInternalChannelId: 922 + VS166ChannelId: 1010 + VS167ChannelId: 1011 + VS168ChannelId: 1154 + VSMasterChannelId: 1012 + VS169ChannelId: 1473 + VS1610ChannelId: 1692 + +stages: +- ${{ if or(and(le(parameters.publishingInfraVersion, 2), eq(parameters.inline, 'true')), eq( parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: + - stage: Validate + dependsOn: ${{ parameters.validateDependsOn }} + displayName: Validate Build Assets + variables: + - template: common-variables.yml + jobs: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - ${{ if and(le(parameters.publishingInfraVersion, 2), eq(parameters.inline, 'true')) }}: + - job: + displayName: Post-build Checks + dependsOn: setupMaestroVars + variables: + - name: TargetChannels + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.TargetChannels'] ] + pool: + vmImage: 'windows-2019' + steps: + - task: PowerShell@2 + displayName: Maestro Channels Consistency + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/check-channel-consistency.ps1 + arguments: -PromoteToChannels "$(TargetChannels)" + -AvailableChannelIds ${{parameters.NetEngLatestChannelId}},${{parameters.NetEngValidationChannelId}},${{parameters.NetDev5ChannelId}},${{parameters.NetDev6ChannelId}},${{parameters.GeneralTestingChannelId}},${{parameters.NETCoreToolingDevChannelId}},${{parameters.NETCoreToolingReleaseChannelId}},${{parameters.NETInternalToolingChannelId}},${{parameters.NETCoreExperimentalChannelId}},${{parameters.NetEngServicesIntChannelId}},${{parameters.NetEngServicesProdChannelId}},${{parameters.NetCoreSDK313xxChannelId}},${{parameters.NetCoreSDK313xxInternalChannelId}},${{parameters.NetCoreSDK314xxChannelId}},${{parameters.NetCoreSDK314xxInternalChannelId}},${{parameters.VS166ChannelId}},${{parameters.VS167ChannelId}},${{parameters.VS168ChannelId}},${{parameters.VSMasterChannelId}},${{parameters.VS169ChannelId}},${{parameters.VS1610ChannelId}} + + - job: + displayName: NuGet Validation + dependsOn: setupMaestroVars + condition: eq( ${{ parameters.enableNugetValidation }}, 'true') + pool: + vmImage: 'windows-2019' + variables: + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] + steps: + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts + checkDownloadedFiles: true + + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/nuget-validation.ps1 + arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ + -ToolDestinationPath $(Agent.BuildDirectory)/Extract/ + + - job: + displayName: Signing Validation + dependsOn: setupMaestroVars + condition: and( eq( ${{ parameters.enableSigningValidation }}, 'true'), ne( variables['PostBuildSign'], 'true')) + variables: + - template: common-variables.yml + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] + pool: + vmImage: 'windows-2019' + steps: + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: PackageArtifacts + checkDownloadedFiles: true + itemPattern: | + ** + !**/Microsoft.SourceBuild.Intermediate.*.nupkg + + # This is necessary whenever we want to publish/restore to an AzDO private feed + # Since sdk-task.ps1 tries to restore packages we need to do this authentication here + # otherwise it'll complain about accessing a private feed. + - task: NuGetAuthenticate@0 + displayName: 'Authenticate to AzDO Feeds' + + - task: PowerShell@2 + displayName: Enable cross-org publishing + inputs: + filePath: eng\common\enable-cross-org-publishing.ps1 + arguments: -token $(dn-bot-dnceng-artifact-feeds-rw) + + # Signing validation will optionally work with the buildmanifest file which is downloaded from + # Azure DevOps above. + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task SigningValidation -restore -msbuildEngine vs + /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' + /p:SignCheckExclusionsFile='$(Build.SourcesDirectory)/eng/SignCheckExclusionsFile.txt' + ${{ parameters.signingValidationAdditionalParameters }} + + - template: ../steps/publish-logs.yml + parameters: + StageLabel: 'Validation' + JobLabel: 'Signing' + + - job: + displayName: SourceLink Validation + dependsOn: setupMaestroVars + condition: eq( ${{ parameters.enableSourceLinkValidation }}, 'true') + variables: + - template: common-variables.yml + - name: AzDOProjectName + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] + - name: AzDOPipelineId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] + - name: AzDOBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] + pool: + vmImage: 'windows-2019' + steps: + - task: DownloadBuildArtifacts@0 + displayName: Download Blob Artifacts + inputs: + buildType: specific + buildVersionToDownload: specific + project: $(AzDOProjectName) + pipeline: $(AzDOPipelineId) + buildId: $(AzDOBuildId) + artifactName: BlobArtifacts + checkDownloadedFiles: true + + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/sourcelink-validation.ps1 + arguments: -InputPath $(Build.ArtifactStagingDirectory)/BlobArtifacts/ + -ExtractPath $(Agent.BuildDirectory)/Extract/ + -GHRepoName $(Build.Repository.Name) + -GHCommit $(Build.SourceVersion) + -SourcelinkCliVersion $(SourceLinkCLIVersion) + continueOnError: true + + - template: /eng/common/templates/job/execute-sdl.yml + parameters: + enable: ${{ parameters.SDLValidationParameters.enable }} + dependsOn: setupMaestroVars + additionalParameters: ${{ parameters.SDLValidationParameters.params }} + continueOnError: ${{ parameters.SDLValidationParameters.continueOnError }} + artifactNames: ${{ parameters.SDLValidationParameters.artifactNames }} + downloadArtifacts: ${{ parameters.SDLValidationParameters.downloadArtifacts }} + +- ${{ if or(ge(parameters.publishingInfraVersion, 3), eq(parameters.inline, 'false')) }}: + - stage: publish_using_darc + ${{ if or(eq(parameters.enableNugetValidation, 'true'), eq(parameters.enableSigningValidation, 'true'), eq(parameters.enableSourceLinkValidation, 'true'), eq(parameters.SDLValidationParameters.enable, 'true')) }}: + dependsOn: ${{ parameters.publishDependsOn }} + ${{ if and(ne(parameters.enableNugetValidation, 'true'), ne(parameters.enableSigningValidation, 'true'), ne(parameters.enableSourceLinkValidation, 'true'), ne(parameters.SDLValidationParameters.enable, 'true')) }}: + dependsOn: ${{ parameters.validateDependsOn }} + displayName: Publish using Darc + variables: + - template: common-variables.yml + jobs: + - template: setup-maestro-vars.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + + - job: + displayName: Publish Using Darc + dependsOn: setupMaestroVars + timeoutInMinutes: 120 + variables: + - name: BARBuildId + value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] + pool: + vmImage: 'windows-2019' + steps: + - task: PowerShell@2 + displayName: Publish Using Darc + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/publish-using-darc.ps1 + arguments: -BuildId $(BARBuildId) + -PublishingInfraVersion ${{ parameters.PublishingInfraVersion }} + -AzdoToken '$(publishing-dnceng-devdiv-code-r-build-re)' + -MaestroToken '$(MaestroApiAccessToken)' + -WaitPublishingFinish ${{ parameters.waitPublishingFinish }} + -PublishInstallersAndChecksums ${{ parameters.publishInstallersAndChecksums }} + -ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}' + -SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}' + +- ${{ if and(le(parameters.publishingInfraVersion, 2), eq(parameters.inline, 'true')) }}: + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NetCore_Dev5_Publish' + channelName: '.NET 5 Dev' + akaMSChannelName: 'net5/dev' + channelId: ${{ parameters.NetDev5ChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NetCore_Dev6_Publish' + channelName: '.NET 6 Dev' + akaMSChannelName: 'net6/dev' + channelId: ${{ parameters.NetDev6ChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'Net_Eng_Latest_Publish' + channelName: '.NET Eng - Latest' + akaMSChannelName: 'eng/daily' + channelId: ${{ parameters.NetEngLatestChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'Net_Eng_Validation_Publish' + channelName: '.NET Eng - Validation' + akaMSChannelName: 'eng/validation' + channelId: ${{ parameters.NetEngValidationChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'General_Testing_Publish' + channelName: 'General Testing' + akaMSChannelName: 'generaltesting' + channelId: ${{ parameters.GeneralTestingChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/general-testing/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/general-testing/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/general-testing-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NETCore_Tooling_Dev_Publishing' + channelName: '.NET Core Tooling Dev' + channelId: ${{ parameters.NETCoreToolingDevChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NETCore_Tooling_Release_Publishing' + channelName: '.NET Core Tooling Release' + channelId: ${{ parameters.NETCoreToolingReleaseChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-internal-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NET_Internal_Tooling_Publishing' + channelName: '.NET Internal Tooling' + channelId: ${{ parameters.NETInternalToolingChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NETCore_Experimental_Publishing' + channelName: '.NET Core Experimental' + channelId: ${{ parameters.NETCoreExperimentalChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'Net_Eng_Services_Int_Publish' + channelName: '.NET Eng Services - Int' + channelId: ${{ parameters.NetEngServicesIntChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'Net_Eng_Services_Prod_Publish' + channelName: '.NET Eng Services - Prod' + channelId: ${{ parameters.NetEngServicesProdChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NETCore_SDK_314xx_Publishing' + channelName: '.NET Core SDK 3.1.4xx' + channelId: ${{ parameters.NetCoreSDK314xxChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-internal-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NETCore_SDK_314xx_Internal_Publishing' + channelName: '.NET Core SDK 3.1.4xx Internal' + channelId: ${{ parameters.NetCoreSDK314xxInternalChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NETCore_SDK_313xx_Publishing' + channelName: '.NET Core SDK 3.1.3xx' + channelId: ${{ parameters.NetCoreSDK313xxChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-internal-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NETCore_SDK_313xx_Internal_Publishing' + channelName: '.NET Core SDK 3.1.3xx Internal' + channelId: ${{ parameters.NetCoreSDK313xxInternalChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'VS16_6_Publishing' + channelName: 'VS 16.6' + channelId: ${{ parameters.VS166ChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'VS16_7_Publishing' + channelName: 'VS 16.7' + channelId: ${{ parameters.VS167ChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'VS16_8_Publishing' + channelName: 'VS 16.8' + channelId: ${{ parameters.VS168ChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'VS_Master_Publishing' + channelName: 'VS Master' + channelId: ${{ parameters.VSMasterChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'VS_16_9_Publishing' + channelName: 'VS 16.9' + channelId: ${{ parameters.VS169ChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' + + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToChannelIds: ${{ parameters.PromoteToChannelIds }} + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + dependsOn: ${{ parameters.publishDependsOn }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'VS_16_10_Publishing' + channelName: 'VS 16.10' + channelId: ${{ parameters.VS1610ChannelId }} + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' diff --git a/eng/common/templates/post-build/setup-maestro-vars.yml b/eng/common/templates/post-build/setup-maestro-vars.yml new file mode 100644 index 000000000..4a22b2e6f --- /dev/null +++ b/eng/common/templates/post-build/setup-maestro-vars.yml @@ -0,0 +1,78 @@ +parameters: + BARBuildId: '' + PromoteToChannelIds: '' + +jobs: +- job: setupMaestroVars + displayName: Setup Maestro Vars + variables: + - template: common-variables.yml + pool: + vmImage: 'windows-2019' + steps: + - checkout: none + + - ${{ if eq(coalesce(parameters.PromoteToChannelIds, 0), 0) }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Release Configs + inputs: + buildType: current + artifactName: ReleaseConfigs + checkDownloadedFiles: true + + - task: PowerShell@2 + name: setReleaseVars + displayName: Set Release Configs Vars + inputs: + targetType: inline + script: | + try { + if (!$Env:PromoteToMaestroChannels -or $Env:PromoteToMaestroChannels.Trim() -eq '') { + $Content = Get-Content $(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt + + $BarId = $Content | Select -Index 0 + $Channels = $Content | Select -Index 1 + $IsStableBuild = $Content | Select -Index 2 + + $AzureDevOpsProject = $Env:System_TeamProject + $AzureDevOpsBuildDefinitionId = $Env:System_DefinitionId + $AzureDevOpsBuildId = $Env:Build_BuildId + } + else { + $buildApiEndpoint = "${Env:MaestroApiEndPoint}/api/builds/${Env:BARBuildId}?api-version=${Env:MaestroApiVersion}" + + $apiHeaders = New-Object 'System.Collections.Generic.Dictionary[[String],[String]]' + $apiHeaders.Add('Accept', 'application/json') + $apiHeaders.Add('Authorization',"Bearer ${Env:MAESTRO_API_TOKEN}") + + $buildInfo = try { Invoke-WebRequest -Method Get -Uri $buildApiEndpoint -Headers $apiHeaders | ConvertFrom-Json } catch { Write-Host "Error: $_" } + + $BarId = $Env:BARBuildId + $Channels = $Env:PromoteToMaestroChannels -split "," + $Channels = $Channels -join "][" + $Channels = "[$Channels]" + + $IsStableBuild = $buildInfo.stable + $AzureDevOpsProject = $buildInfo.azureDevOpsProject + $AzureDevOpsBuildDefinitionId = $buildInfo.azureDevOpsBuildDefinitionId + $AzureDevOpsBuildId = $buildInfo.azureDevOpsBuildId + } + + Write-Host "##vso[task.setvariable variable=BARBuildId;isOutput=true]$BarId" + Write-Host "##vso[task.setvariable variable=TargetChannels;isOutput=true]$Channels" + Write-Host "##vso[task.setvariable variable=IsStableBuild;isOutput=true]$IsStableBuild" + + Write-Host "##vso[task.setvariable variable=AzDOProjectName;isOutput=true]$AzureDevOpsProject" + Write-Host "##vso[task.setvariable variable=AzDOPipelineId;isOutput=true]$AzureDevOpsBuildDefinitionId" + Write-Host "##vso[task.setvariable variable=AzDOBuildId;isOutput=true]$AzureDevOpsBuildId" + } + catch { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + exit 1 + } + env: + MAESTRO_API_TOKEN: $(MaestroApiAccessToken) + BARBuildId: ${{ parameters.BARBuildId }} + PromoteToMaestroChannels: ${{ parameters.PromoteToChannelIds }} diff --git a/eng/common/templates/post-build/trigger-subscription.yml b/eng/common/templates/post-build/trigger-subscription.yml new file mode 100644 index 000000000..da669030d --- /dev/null +++ b/eng/common/templates/post-build/trigger-subscription.yml @@ -0,0 +1,13 @@ +parameters: + ChannelId: 0 + +steps: +- task: PowerShell@2 + displayName: Triggering subscriptions + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/trigger-subscriptions.ps1 + arguments: -SourceRepo $(Build.Repository.Uri) + -ChannelId ${{ parameters.ChannelId }} + -MaestroApiAccessToken $(MaestroAccessToken) + -MaestroApiEndPoint $(MaestroApiEndPoint) + -MaestroApiVersion $(MaestroApiVersion) diff --git a/eng/common/templates/steps/add-build-to-channel.yml b/eng/common/templates/steps/add-build-to-channel.yml new file mode 100644 index 000000000..f67a210d6 --- /dev/null +++ b/eng/common/templates/steps/add-build-to-channel.yml @@ -0,0 +1,13 @@ +parameters: + ChannelId: 0 + +steps: +- task: PowerShell@2 + displayName: Add Build to Channel + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/add-build-to-channel.ps1 + arguments: -BuildId $(BARBuildId) + -ChannelId ${{ parameters.ChannelId }} + -MaestroApiAccessToken $(MaestroApiAccessToken) + -MaestroApiEndPoint $(MaestroApiEndPoint) + -MaestroApiVersion $(MaestroApiVersion) diff --git a/eng/common/templates/steps/build-reason.yml b/eng/common/templates/steps/build-reason.yml new file mode 100644 index 000000000..eba58109b --- /dev/null +++ b/eng/common/templates/steps/build-reason.yml @@ -0,0 +1,12 @@ +# build-reason.yml +# Description: runs steps if build.reason condition is valid. conditions is a string of valid build reasons +# to include steps (',' separated). +parameters: + conditions: '' + steps: [] + +steps: + - ${{ if and( not(startsWith(parameters.conditions, 'not')), contains(parameters.conditions, variables['build.reason'])) }}: + - ${{ parameters.steps }} + - ${{ if and( startsWith(parameters.conditions, 'not'), not(contains(parameters.conditions, variables['build.reason']))) }}: + - ${{ parameters.steps }} diff --git a/eng/common/templates/steps/publish-logs.yml b/eng/common/templates/steps/publish-logs.yml new file mode 100644 index 000000000..88f238f36 --- /dev/null +++ b/eng/common/templates/steps/publish-logs.yml @@ -0,0 +1,23 @@ +parameters: + StageLabel: '' + JobLabel: '' + +steps: +- task: Powershell@2 + displayName: Prepare Binlogs to Upload + inputs: + targetType: inline + script: | + New-Item -ItemType Directory $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ + Move-Item -Path $(Build.SourcesDirectory)/artifacts/log/Debug/* $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ + continueOnError: true + condition: always() + +- task: PublishBuildArtifacts@1 + displayName: Publish Logs + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/PostBuildLogs' + PublishLocation: Container + ArtifactName: PostBuildLogs + continueOnError: true + condition: always() diff --git a/eng/common/templates/steps/run-on-unix.yml b/eng/common/templates/steps/run-on-unix.yml new file mode 100644 index 000000000..e1733814f --- /dev/null +++ b/eng/common/templates/steps/run-on-unix.yml @@ -0,0 +1,7 @@ +parameters: + agentOs: '' + steps: [] + +steps: +- ${{ if ne(parameters.agentOs, 'Windows_NT') }}: + - ${{ parameters.steps }} diff --git a/eng/common/templates/steps/run-on-windows.yml b/eng/common/templates/steps/run-on-windows.yml new file mode 100644 index 000000000..73e7e9c27 --- /dev/null +++ b/eng/common/templates/steps/run-on-windows.yml @@ -0,0 +1,7 @@ +parameters: + agentOs: '' + steps: [] + +steps: +- ${{ if eq(parameters.agentOs, 'Windows_NT') }}: + - ${{ parameters.steps }} diff --git a/eng/common/templates/steps/run-script-ifequalelse.yml b/eng/common/templates/steps/run-script-ifequalelse.yml new file mode 100644 index 000000000..3d1242f55 --- /dev/null +++ b/eng/common/templates/steps/run-script-ifequalelse.yml @@ -0,0 +1,33 @@ +parameters: + # if parameter1 equals parameter 2, run 'ifScript' command, else run 'elsescript' command + parameter1: '' + parameter2: '' + ifScript: '' + elseScript: '' + + # name of script step + name: Script + + # display name of script step + displayName: If-Equal-Else Script + + # environment + env: {} + + # conditional expression for step execution + condition: '' + +steps: +- ${{ if and(ne(parameters.ifScript, ''), eq(parameters.parameter1, parameters.parameter2)) }}: + - script: ${{ parameters.ifScript }} + name: ${{ parameters.name }} + displayName: ${{ parameters.displayName }} + env: ${{ parameters.env }} + condition: ${{ parameters.condition }} + +- ${{ if and(ne(parameters.elseScript, ''), ne(parameters.parameter1, parameters.parameter2)) }}: + - script: ${{ parameters.elseScript }} + name: ${{ parameters.name }} + displayName: ${{ parameters.displayName }} + env: ${{ parameters.env }} + condition: ${{ parameters.condition }} \ No newline at end of file diff --git a/eng/common/templates/steps/send-to-helix.yml b/eng/common/templates/steps/send-to-helix.yml new file mode 100644 index 000000000..cd02ae160 --- /dev/null +++ b/eng/common/templates/steps/send-to-helix.yml @@ -0,0 +1,94 @@ +# Please remember to update the documentation if you make changes to these parameters! +parameters: + HelixSource: 'pr/default' # required -- sources must start with pr/, official/, prodcon/, or agent/ + HelixType: 'tests/default/' # required -- Helix telemetry which identifies what type of data this is; should include "test" for clarity and must end in '/' + HelixBuild: $(Build.BuildNumber) # required -- the build number Helix will use to identify this -- automatically set to the AzDO build number + HelixTargetQueues: '' # required -- semicolon delimited list of Helix queues to test on; see https://helix.dot.net/ for a list of queues + HelixAccessToken: '' # required -- access token to make Helix API requests; should be provided by the appropriate variable group + HelixConfiguration: '' # optional -- additional property attached to a job + HelixPreCommands: '' # optional -- commands to run before Helix work item execution + HelixPostCommands: '' # optional -- commands to run after Helix work item execution + WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects + WorkItemCommand: '' # optional -- a command to execute on the payload; requires WorkItemDirectory; incompatible with XUnitProjects + WorkItemTimeout: '' # optional -- a timeout in TimeSpan.Parse-ready value (e.g. 00:02:00) for the work item command; requires WorkItemDirectory; incompatible with XUnitProjects + CorrelationPayloadDirectory: '' # optional -- a directory to zip up and send to Helix as a correlation payload + XUnitProjects: '' # optional -- semicolon delimited list of XUnitProjects to parse and send to Helix; requires XUnitRuntimeTargetFramework, XUnitPublishTargetFramework, XUnitRunnerVersion, and IncludeDotNetCli=true + XUnitWorkItemTimeout: '' # optional -- the workitem timeout in seconds for all workitems created from the xUnit projects specified by XUnitProjects + XUnitPublishTargetFramework: '' # optional -- framework to use to publish your xUnit projects + XUnitRuntimeTargetFramework: '' # optional -- framework to use for the xUnit console runner + XUnitRunnerVersion: '' # optional -- version of the xUnit nuget package you wish to use on Helix; required for XUnitProjects + IncludeDotNetCli: false # optional -- true will download a version of the .NET CLI onto the Helix machine as a correlation payload; requires DotNetCliPackageType and DotNetCliVersion + DotNetCliPackageType: '' # optional -- either 'sdk', 'runtime' or 'aspnetcore-runtime'; determines whether the sdk or runtime will be sent to Helix; see https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases-index.json + DotNetCliVersion: '' # optional -- version of the CLI to send to Helix; based on this: https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases-index.json + EnableXUnitReporter: false # optional -- true enables XUnit result reporting to Mission Control + WaitForWorkItemCompletion: true # optional -- true will make the task wait until work items have been completed and fail the build if work items fail. False is "fire and forget." + IsExternal: false # [DEPRECATED] -- doesn't do anything, jobs are external if HelixAccessToken is empty and Creator is set + HelixBaseUri: 'https://helix.dot.net/' # optional -- sets the Helix API base URI (allows targeting int) + Creator: '' # optional -- if the build is external, use this to specify who is sending the job + DisplayNamePrefix: 'Run Tests' # optional -- rename the beginning of the displayName of the steps in AzDO + condition: succeeded() # optional -- condition for step to execute; defaults to succeeded() + continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false + +steps: + - powershell: 'powershell "$env:BUILD_SOURCESDIRECTORY\eng\common\msbuild.ps1 $env:BUILD_SOURCESDIRECTORY\eng\common\helixpublish.proj /restore /t:Test /bl:$env:BUILD_SOURCESDIRECTORY\artifacts\log\$env:BuildConfig\SendToHelix.binlog"' + displayName: ${{ parameters.DisplayNamePrefix }} (Windows) + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} + HelixAccessToken: ${{ parameters.HelixAccessToken }} + HelixPreCommands: ${{ parameters.HelixPreCommands }} + HelixPostCommands: ${{ parameters.HelixPostCommands }} + WorkItemDirectory: ${{ parameters.WorkItemDirectory }} + WorkItemCommand: ${{ parameters.WorkItemCommand }} + WorkItemTimeout: ${{ parameters.WorkItemTimeout }} + CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} + XUnitProjects: ${{ parameters.XUnitProjects }} + XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} + XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} + XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} + XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} + IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} + DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} + DotNetCliVersion: ${{ parameters.DotNetCliVersion }} + EnableXUnitReporter: ${{ parameters.EnableXUnitReporter }} + WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} + Creator: ${{ parameters.Creator }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} + - script: $BUILD_SOURCESDIRECTORY/eng/common/msbuild.sh $BUILD_SOURCESDIRECTORY/eng/common/helixpublish.proj /restore /t:Test /bl:$BUILD_SOURCESDIRECTORY/artifacts/log/$BuildConfig/SendToHelix.binlog + displayName: ${{ parameters.DisplayNamePrefix }} (Unix) + env: + BuildConfig: $(_BuildConfig) + HelixSource: ${{ parameters.HelixSource }} + HelixType: ${{ parameters.HelixType }} + HelixBuild: ${{ parameters.HelixBuild }} + HelixConfiguration: ${{ parameters.HelixConfiguration }} + HelixTargetQueues: ${{ parameters.HelixTargetQueues }} + HelixAccessToken: ${{ parameters.HelixAccessToken }} + HelixPreCommands: ${{ parameters.HelixPreCommands }} + HelixPostCommands: ${{ parameters.HelixPostCommands }} + WorkItemDirectory: ${{ parameters.WorkItemDirectory }} + WorkItemCommand: ${{ parameters.WorkItemCommand }} + WorkItemTimeout: ${{ parameters.WorkItemTimeout }} + CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} + XUnitProjects: ${{ parameters.XUnitProjects }} + XUnitWorkItemTimeout: ${{ parameters.XUnitWorkItemTimeout }} + XUnitPublishTargetFramework: ${{ parameters.XUnitPublishTargetFramework }} + XUnitRuntimeTargetFramework: ${{ parameters.XUnitRuntimeTargetFramework }} + XUnitRunnerVersion: ${{ parameters.XUnitRunnerVersion }} + IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} + DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} + DotNetCliVersion: ${{ parameters.DotNetCliVersion }} + EnableXUnitReporter: ${{ parameters.EnableXUnitReporter }} + WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} + HelixBaseUri: ${{ parameters.HelixBaseUri }} + Creator: ${{ parameters.Creator }} + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT')) + continueOnError: ${{ parameters.continueOnError }} diff --git a/eng/common/templates/steps/source-build.yml b/eng/common/templates/steps/source-build.yml new file mode 100644 index 000000000..e20637ed6 --- /dev/null +++ b/eng/common/templates/steps/source-build.yml @@ -0,0 +1,71 @@ +parameters: + # This template adds arcade-powered source-build to CI. + + # This is a 'steps' template, and is intended for advanced scenarios where the existing build + # infra has a careful build methodology that must be followed. For example, a repo + # (dotnet/runtime) might choose to clone the GitHub repo only once and store it as a pipeline + # artifact for all subsequent jobs to use, to reduce dependence on a strong network connection to + # GitHub. Using this steps template leaves room for that infra to be included. + + # Defines the platform on which to run the steps. See 'eng/common/templates/job/source-build.yml' + # for details. The entire object is described in the 'job' template for simplicity, even though + # the usage of the properties on this object is split between the 'job' and 'steps' templates. + platform: {} + +steps: +# Build. Keep it self-contained for simple reusability. (No source-build-specific job variables.) +- script: | + set -x + df -h + + buildConfig=Release + # Check if AzDO substitutes in a build config from a variable, and use it if so. + if [ '$(_BuildConfig)' != '$''(_BuildConfig)' ]; then + buildConfig='$(_BuildConfig)' + fi + + officialBuildArgs= + if [ '${{ and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}' = 'True' ]; then + officialBuildArgs='/p:DotNetPublishUsingPipelines=true /p:OfficialBuildId=$(BUILD.BUILDNUMBER)' + fi + + targetRidArgs= + if [ '${{ parameters.platform.targetRID }}' != '' ]; then + targetRidArgs='/p:TargetRid=${{ parameters.platform.targetRID }}' + fi + + publishArgs= + if [ '${{ parameters.platform.skipPublishValidation }}' != 'true' ]; then + publishArgs='--publish' + fi + + ${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \ + --configuration $buildConfig \ + --restore --build --pack $publishArgs -bl \ + $officialBuildArgs \ + $targetRidArgs \ + /p:SourceBuildNonPortable=${{ parameters.platform.nonPortable }} \ + /p:ArcadeBuildFromSource=true + displayName: Build + +# Upload build logs for diagnosis. +- task: CopyFiles@2 + displayName: Prepare BuildLogs staging directory + inputs: + SourceFolder: '$(Build.SourcesDirectory)' + Contents: | + **/*.log + **/*.binlog + artifacts/source-build/self/prebuilt-report/** + TargetFolder: '$(Build.StagingDirectory)/BuildLogs' + CleanTargetFolder: true + continueOnError: true + condition: succeededOrFailed() + +- task: PublishPipelineArtifact@1 + displayName: Publish BuildLogs + inputs: + targetPath: '$(Build.StagingDirectory)/BuildLogs' + artifactName: BuildLogs_SourceBuild_${{ parameters.platform.name }}_Attempt$(System.JobAttempt) + continueOnError: true + condition: succeededOrFailed() diff --git a/eng/common/templates/steps/telemetry-end.yml b/eng/common/templates/steps/telemetry-end.yml new file mode 100644 index 000000000..fadc04ca1 --- /dev/null +++ b/eng/common/templates/steps/telemetry-end.yml @@ -0,0 +1,102 @@ +parameters: + maxRetries: 5 + retryDelay: 10 # in seconds + +steps: +- bash: | + if [ "$AGENT_JOBSTATUS" = "Succeeded" ] || [ "$AGENT_JOBSTATUS" = "PartiallySucceeded" ]; then + errorCount=0 + else + errorCount=1 + fi + warningCount=0 + + curlStatus=1 + retryCount=0 + # retry loop to harden against spotty telemetry connections + # we don't retry successes and 4xx client errors + until [[ $curlStatus -eq 0 || ( $curlStatus -ge 400 && $curlStatus -le 499 ) || $retryCount -ge $MaxRetries ]] + do + if [ $retryCount -gt 0 ]; then + echo "Failed to send telemetry to Helix; waiting $RetryDelay seconds before retrying..." + sleep $RetryDelay + fi + + # create a temporary file for curl output + res=`mktemp` + + curlResult=` + curl --verbose --output $res --write-out "%{http_code}"\ + -H 'Content-Type: application/json' \ + -H "X-Helix-Job-Token: $Helix_JobToken" \ + -H 'Content-Length: 0' \ + -X POST -G "https://helix.dot.net/api/2018-03-14/telemetry/job/build/$Helix_WorkItemId/finish" \ + --data-urlencode "errorCount=$errorCount" \ + --data-urlencode "warningCount=$warningCount"` + curlStatus=$? + + if [ $curlStatus -eq 0 ]; then + if [ $curlResult -gt 299 ] || [ $curlResult -lt 200 ]; then + curlStatus=$curlResult + fi + fi + + let retryCount++ + done + + if [ $curlStatus -ne 0 ]; then + echo "Failed to Send Build Finish information after $retryCount retries" + vstsLogOutput="vso[task.logissue type=error;sourcepath=templates/steps/telemetry-end.yml;code=1;]Failed to Send Build Finish information: $curlStatus" + echo "##$vstsLogOutput" + exit 1 + fi + displayName: Send Unix Build End Telemetry + env: + # defined via VSTS variables in start-job.sh + Helix_JobToken: $(Helix_JobToken) + Helix_WorkItemId: $(Helix_WorkItemId) + MaxRetries: ${{ parameters.maxRetries }} + RetryDelay: ${{ parameters.retryDelay }} + condition: and(always(), ne(variables['Agent.Os'], 'Windows_NT')) +- powershell: | + if (($env:Agent_JobStatus -eq 'Succeeded') -or ($env:Agent_JobStatus -eq 'PartiallySucceeded')) { + $ErrorCount = 0 + } else { + $ErrorCount = 1 + } + $WarningCount = 0 + + # Basic retry loop to harden against server flakiness + $retryCount = 0 + while ($retryCount -lt $env:MaxRetries) { + try { + Invoke-RestMethod -Uri "https://helix.dot.net/api/2018-03-14/telemetry/job/build/$env:Helix_WorkItemId/finish?errorCount=$ErrorCount&warningCount=$WarningCount" -Method Post -ContentType "application/json" -Body "" ` + -Headers @{ 'X-Helix-Job-Token'=$env:Helix_JobToken } + break + } + catch { + $statusCode = $_.Exception.Response.StatusCode.value__ + if ($statusCode -ge 400 -and $statusCode -le 499) { + Write-Host "##vso[task.logissue]error Failed to send telemetry to Helix (status code $statusCode); not retrying (4xx client error)" + Write-Host "##vso[task.logissue]error ", $_.Exception.GetType().FullName, $_.Exception.Message + exit 1 + } + Write-Host "Failed to send telemetry to Helix (status code $statusCode); waiting $env:RetryDelay seconds before retrying..." + $retryCount++ + sleep $env:RetryDelay + continue + } + } + + if ($retryCount -ge $env:MaxRetries) { + Write-Host "##vso[task.logissue]error Failed to send telemetry to Helix after $retryCount retries." + exit 1 + } + displayName: Send Windows Build End Telemetry + env: + # defined via VSTS variables in start-job.ps1 + Helix_JobToken: $(Helix_JobToken) + Helix_WorkItemId: $(Helix_WorkItemId) + MaxRetries: ${{ parameters.maxRetries }} + RetryDelay: ${{ parameters.retryDelay }} + condition: and(always(),eq(variables['Agent.Os'], 'Windows_NT')) diff --git a/eng/common/templates/steps/telemetry-start.yml b/eng/common/templates/steps/telemetry-start.yml new file mode 100644 index 000000000..32c01ef0b --- /dev/null +++ b/eng/common/templates/steps/telemetry-start.yml @@ -0,0 +1,241 @@ +parameters: + helixSource: 'undefined_defaulted_in_telemetry.yml' + helixType: 'undefined_defaulted_in_telemetry.yml' + buildConfig: '' + runAsPublic: false + maxRetries: 5 + retryDelay: 10 # in seconds + +steps: +- ${{ if and(eq(parameters.runAsPublic, 'false'), not(eq(variables['System.TeamProject'], 'public'))) }}: + - task: AzureKeyVault@1 + inputs: + azureSubscription: 'HelixProd_KeyVault' + KeyVaultName: HelixProdKV + SecretsFilter: 'HelixApiAccessToken' + condition: always() +- bash: | + # create a temporary file + jobInfo=`mktemp` + + # write job info content to temporary file + cat > $jobInfo < powershell invocations +# as dot sourcing isn't possible. +function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { + if (Test-Path variable:global:_DotNetInstallDir) { + return $global:_DotNetInstallDir + } + + # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism + $env:DOTNET_MULTILEVEL_LOOKUP=0 + + # Disable first run since we do not need all ASP.NET packages restored. + $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + + # Disable telemetry on CI. + if ($ci) { + $env:DOTNET_CLI_TELEMETRY_OPTOUT=1 + } + + # Source Build uses DotNetCoreSdkDir variable + if ($env:DotNetCoreSdkDir -ne $null) { + $env:DOTNET_INSTALL_DIR = $env:DotNetCoreSdkDir + } + + # Find the first path on %PATH% that contains the dotnet.exe + if ($useInstalledDotNetCli -and (-not $globalJsonHasRuntimes) -and ($env:DOTNET_INSTALL_DIR -eq $null)) { + $dotnetExecutable = GetExecutableFileName 'dotnet' + $dotnetCmd = Get-Command $dotnetExecutable -ErrorAction SilentlyContinue + + if ($dotnetCmd -ne $null) { + $env:DOTNET_INSTALL_DIR = Split-Path $dotnetCmd.Path -Parent + } + } + + $dotnetSdkVersion = $GlobalJson.tools.dotnet + + # Use dotnet installation specified in DOTNET_INSTALL_DIR if it contains the required SDK version, + # otherwise install the dotnet CLI and SDK to repo local .dotnet directory to avoid potential permission issues. + if ((-not $globalJsonHasRuntimes) -and (-not [string]::IsNullOrEmpty($env:DOTNET_INSTALL_DIR)) -and (Test-Path(Join-Path $env:DOTNET_INSTALL_DIR "sdk\$dotnetSdkVersion"))) { + $dotnetRoot = $env:DOTNET_INSTALL_DIR + } else { + $dotnetRoot = Join-Path $RepoRoot '.dotnet' + + if (-not (Test-Path(Join-Path $dotnetRoot "sdk\$dotnetSdkVersion"))) { + if ($install) { + InstallDotNetSdk $dotnetRoot $dotnetSdkVersion + } else { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unable to find dotnet with SDK version '$dotnetSdkVersion'" + ExitWithExitCode 1 + } + } + + $env:DOTNET_INSTALL_DIR = $dotnetRoot + } + + # Creates a temporary file under the toolset dir. + # The following code block is protecting against concurrent access so that this function can + # be called in parallel. + if ($createSdkLocationFile) { + do { + $sdkCacheFileTemp = Join-Path $ToolsetDir $([System.IO.Path]::GetRandomFileName()) + } + until (!(Test-Path $sdkCacheFileTemp)) + Set-Content -Path $sdkCacheFileTemp -Value $dotnetRoot + + try { + Move-Item -Force $sdkCacheFileTemp (Join-Path $ToolsetDir 'sdk.txt') + } catch { + # Somebody beat us + Remove-Item -Path $sdkCacheFileTemp + } + } + + # Add dotnet to PATH. This prevents any bare invocation of dotnet in custom + # build steps from using anything other than what we've downloaded. + # It also ensures that VS msbuild will use the downloaded sdk targets. + $env:PATH = "$dotnetRoot;$env:PATH" + + # Make Sure that our bootstrapped dotnet cli is available in future steps of the Azure Pipelines build + Write-PipelinePrependPath -Path $dotnetRoot + + Write-PipelineSetVariable -Name 'DOTNET_MULTILEVEL_LOOKUP' -Value '0' + Write-PipelineSetVariable -Name 'DOTNET_SKIP_FIRST_TIME_EXPERIENCE' -Value '1' + + return $global:_DotNetInstallDir = $dotnetRoot +} + +function GetDotNetInstallScript([string] $dotnetRoot) { + $installScript = Join-Path $dotnetRoot 'dotnet-install.ps1' + if (!(Test-Path $installScript)) { + Create-Directory $dotnetRoot + $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit + + $maxRetries = 5 + $retries = 1 + + $uri = "https://dot.net/$dotnetInstallScriptVersion/dotnet-install.ps1" + + while($true) { + try { + Write-Host "GET $uri" + Invoke-WebRequest $uri -OutFile $installScript + break + } + catch { + Write-Host "Failed to download '$uri'" + Write-Error $_.Exception.Message -ErrorAction Continue + } + + if (++$retries -le $maxRetries) { + $delayInSeconds = [math]::Pow(2, $retries) - 1 # Exponential backoff + Write-Host "Retrying. Waiting for $delayInSeconds seconds before next attempt ($retries of $maxRetries)." + Start-Sleep -Seconds $delayInSeconds + } + else { + throw "Unable to download file in $maxRetries attempts." + } + + } + } + + return $installScript +} + +function InstallDotNetSdk([string] $dotnetRoot, [string] $version, [string] $architecture = '', [switch] $noPath) { + InstallDotNet $dotnetRoot $version $architecture '' $false $runtimeSourceFeed $runtimeSourceFeedKey -noPath:$noPath +} + +function InstallDotNet([string] $dotnetRoot, + [string] $version, + [string] $architecture = '', + [string] $runtime = '', + [bool] $skipNonVersionedFiles = $false, + [string] $runtimeSourceFeed = '', + [string] $runtimeSourceFeedKey = '', + [switch] $noPath) { + + $installScript = GetDotNetInstallScript $dotnetRoot + $installParameters = @{ + Version = $version + InstallDir = $dotnetRoot + } + + if ($architecture) { $installParameters.Architecture = $architecture } + if ($runtime) { $installParameters.Runtime = $runtime } + if ($skipNonVersionedFiles) { $installParameters.SkipNonVersionedFiles = $skipNonVersionedFiles } + if ($noPath) { $installParameters.NoPath = $True } + + try { + & $installScript @installParameters + } + catch { + if ($runtimeSourceFeed -or $runtimeSourceFeedKey) { + Write-Host "Failed to install dotnet from public location. Trying from '$runtimeSourceFeed'" + if ($runtimeSourceFeed) { $installParameters.AzureFeed = $runtimeSourceFeed } + + if ($runtimeSourceFeedKey) { + $decodedBytes = [System.Convert]::FromBase64String($runtimeSourceFeedKey) + $decodedString = [System.Text.Encoding]::UTF8.GetString($decodedBytes) + $installParameters.FeedCredential = $decodedString + } + + try { + & $installScript @installParameters + } + catch { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Failed to install dotnet from custom location '$runtimeSourceFeed'." + ExitWithExitCode 1 + } + } else { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Failed to install dotnet from public location." + ExitWithExitCode 1 + } + } +} + +# +# Locates Visual Studio MSBuild installation. +# The preference order for MSBuild to use is as follows: +# +# 1. MSBuild from an active VS command prompt +# 2. MSBuild from a compatible VS installation +# 3. MSBuild from the xcopy tool package +# +# Returns full path to msbuild.exe. +# Throws on failure. +# +function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = $null) { + if (-not (IsWindowsPlatform)) { + throw "Cannot initialize Visual Studio on non-Windows" + } + + if (Test-Path variable:global:_MSBuildExe) { + return $global:_MSBuildExe + } + + # Minimum VS version to require. + $vsMinVersionReqdStr = '16.8' + $vsMinVersionReqd = [Version]::new($vsMinVersionReqdStr) + + # If the version of msbuild is going to be xcopied, + # use this version. Version matches a package here: + # https://dev.azure.com/dnceng/public/_packaging?_a=package&feed=dotnet-eng&package=RoslynTools.MSBuild&protocolType=NuGet&version=16.8.0-preview3&view=overview + $defaultXCopyMSBuildVersion = '16.8.0-preview3' + + if (!$vsRequirements) { $vsRequirements = $GlobalJson.tools.vs } + $vsMinVersionStr = if ($vsRequirements.version) { $vsRequirements.version } else { $vsMinVersionReqdStr } + $vsMinVersion = [Version]::new($vsMinVersionStr) + + # Try msbuild command available in the environment. + if ($env:VSINSTALLDIR -ne $null) { + $msbuildCmd = Get-Command 'msbuild.exe' -ErrorAction SilentlyContinue + if ($msbuildCmd -ne $null) { + # Workaround for https://github.com/dotnet/roslyn/issues/35793 + # Due to this issue $msbuildCmd.Version returns 0.0.0.0 for msbuild.exe 16.2+ + $msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split([char[]]@('-', '+'))[0]) + + if ($msbuildVersion -ge $vsMinVersion) { + return $global:_MSBuildExe = $msbuildCmd.Path + } + + # Report error - the developer environment is initialized with incompatible VS version. + throw "Developer Command Prompt for VS $($env:VisualStudioVersion) is not recent enough. Please upgrade to $vsMinVersionStr or build from a plain CMD window" + } + } + + # Locate Visual Studio installation or download x-copy msbuild. + $vsInfo = LocateVisualStudio $vsRequirements + if ($vsInfo -ne $null) { + $vsInstallDir = $vsInfo.installationPath + $vsMajorVersion = $vsInfo.installationVersion.Split('.')[0] + + InitializeVisualStudioEnvironmentVariables $vsInstallDir $vsMajorVersion + } else { + + if (Get-Member -InputObject $GlobalJson.tools -Name 'xcopy-msbuild') { + $xcopyMSBuildVersion = $GlobalJson.tools.'xcopy-msbuild' + $vsMajorVersion = $xcopyMSBuildVersion.Split('.')[0] + } else { + #if vs version provided in global.json is incompatible (too low) then use the default version for xcopy msbuild download + if($vsMinVersion -lt $vsMinVersionReqd){ + Write-Host "Using xcopy-msbuild version of $defaultXCopyMSBuildVersion since VS version $vsMinVersionStr provided in global.json is not compatible" + $xcopyMSBuildVersion = $defaultXCopyMSBuildVersion + } + else{ + # If the VS version IS compatible, look for an xcopy msbuild package + # with a version matching VS. + # Note: If this version does not exist, then an explicit version of xcopy msbuild + # can be specified in global.json. This will be required for pre-release versions of msbuild. + $vsMajorVersion = $vsMinVersion.Major + $vsMinorVersion = $vsMinVersion.Minor + $xcopyMSBuildVersion = "$vsMajorVersion.$vsMinorVersion.0" + } + } + + $vsInstallDir = $null + if ($xcopyMSBuildVersion.Trim() -ine "none") { + $vsInstallDir = InitializeXCopyMSBuild $xcopyMSBuildVersion $install + if ($vsInstallDir -eq $null) { + throw "Could not xcopy msbuild. Please check that package 'RoslynTools.MSBuild @ $xcopyMSBuildVersion' exists on feed 'dotnet-eng'." + } + } + if ($vsInstallDir -eq $null) { + throw 'Unable to find Visual Studio that has required version and components installed' + } + } + + $msbuildVersionDir = if ([int]$vsMajorVersion -lt 16) { "$vsMajorVersion.0" } else { "Current" } + return $global:_MSBuildExe = Join-Path $vsInstallDir "MSBuild\$msbuildVersionDir\Bin\msbuild.exe" +} + +function InitializeVisualStudioEnvironmentVariables([string] $vsInstallDir, [string] $vsMajorVersion) { + $env:VSINSTALLDIR = $vsInstallDir + Set-Item "env:VS$($vsMajorVersion)0COMNTOOLS" (Join-Path $vsInstallDir "Common7\Tools\") + + $vsSdkInstallDir = Join-Path $vsInstallDir "VSSDK\" + if (Test-Path $vsSdkInstallDir) { + Set-Item "env:VSSDK$($vsMajorVersion)0Install" $vsSdkInstallDir + $env:VSSDKInstall = $vsSdkInstallDir + } +} + +function InstallXCopyMSBuild([string]$packageVersion) { + return InitializeXCopyMSBuild $packageVersion -install $true +} + +function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) { + $packageName = 'RoslynTools.MSBuild' + $packageDir = Join-Path $ToolsDir "msbuild\$packageVersion" + $packagePath = Join-Path $packageDir "$packageName.$packageVersion.nupkg" + + if (!(Test-Path $packageDir)) { + if (!$install) { + return $null + } + + Create-Directory $packageDir + Write-Host "Downloading $packageName $packageVersion" + $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit + Invoke-WebRequest "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/flat2/$packageName/$packageVersion/$packageName.$packageVersion.nupkg" -OutFile $packagePath + Unzip $packagePath $packageDir + } + + return Join-Path $packageDir 'tools' +} + +# +# Locates Visual Studio instance that meets the minimal requirements specified by tools.vs object in global.json. +# +# The following properties of tools.vs are recognized: +# "version": "{major}.{minor}" +# Two part minimal VS version, e.g. "15.9", "16.0", etc. +# "components": ["componentId1", "componentId2", ...] +# Array of ids of workload components that must be available in the VS instance. +# See e.g. https://docs.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-enterprise?view=vs-2017 +# +# Returns JSON describing the located VS instance (same format as returned by vswhere), +# or $null if no instance meeting the requirements is found on the machine. +# +function LocateVisualStudio([object]$vsRequirements = $null){ + if (-not (IsWindowsPlatform)) { + throw "Cannot run vswhere on non-Windows platforms." + } + + if (Get-Member -InputObject $GlobalJson.tools -Name 'vswhere') { + $vswhereVersion = $GlobalJson.tools.vswhere + } else { + $vswhereVersion = '2.5.2' + } + + $vsWhereDir = Join-Path $ToolsDir "vswhere\$vswhereVersion" + $vsWhereExe = Join-Path $vsWhereDir 'vswhere.exe' + + if (!(Test-Path $vsWhereExe)) { + Create-Directory $vsWhereDir + Write-Host 'Downloading vswhere' + $maxRetries = 5 + $retries = 1 + + while($true) { + try { + Invoke-WebRequest "https://netcorenativeassets.blob.core.windows.net/resource-packages/external/windows/vswhere/$vswhereVersion/vswhere.exe" -OutFile $vswhereExe + break + } + catch{ + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ + } + + if (++$retries -le $maxRetries) { + $delayInSeconds = [math]::Pow(2, $retries) - 1 # Exponential backoff + Write-Host "Retrying. Waiting for $delayInSeconds seconds before next attempt ($retries of $maxRetries)." + Start-Sleep -Seconds $delayInSeconds + } + else { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unable to download file in $maxRetries attempts." + } + } + } + + if (!$vsRequirements) { $vsRequirements = $GlobalJson.tools.vs } + $args = @('-latest', '-format', 'json', '-requires', 'Microsoft.Component.MSBuild', '-products', '*') + + if (!$excludePrereleaseVS) { + $args += '-prerelease' + } + + if (Get-Member -InputObject $vsRequirements -Name 'version') { + $args += '-version' + $args += $vsRequirements.version + } + + if (Get-Member -InputObject $vsRequirements -Name 'components') { + foreach ($component in $vsRequirements.components) { + $args += '-requires' + $args += $component + } + } + + $vsInfo =& $vsWhereExe $args | ConvertFrom-Json + + if ($lastExitCode -ne 0) { + return $null + } + + # use first matching instance + return $vsInfo[0] +} + +function InitializeBuildTool() { + if (Test-Path variable:global:_BuildTool) { + # If the requested msbuild parameters do not match, clear the cached variables. + if($global:_BuildTool.Contains('ExcludePrereleaseVS') -and $global:_BuildTool.ExcludePrereleaseVS -ne $excludePrereleaseVS) { + Remove-Item variable:global:_BuildTool + Remove-Item variable:global:_MSBuildExe + } else { + return $global:_BuildTool + } + } + + if (-not $msbuildEngine) { + $msbuildEngine = GetDefaultMSBuildEngine + } + + # Initialize dotnet cli if listed in 'tools' + $dotnetRoot = $null + if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') { + $dotnetRoot = InitializeDotNetCli -install:$restore + } + + if ($msbuildEngine -eq 'dotnet') { + if (!$dotnetRoot) { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "/global.json must specify 'tools.dotnet'." + ExitWithExitCode 1 + } + $dotnetPath = Join-Path $dotnetRoot (GetExecutableFileName 'dotnet') + $buildTool = @{ Path = $dotnetPath; Command = 'msbuild'; Tool = 'dotnet'; Framework = 'netcoreapp3.1' } + } elseif ($msbuildEngine -eq "vs") { + try { + $msbuildPath = InitializeVisualStudioMSBuild -install:$restore + } catch { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ + ExitWithExitCode 1 + } + + $buildTool = @{ Path = $msbuildPath; Command = ""; Tool = "vs"; Framework = "net472"; ExcludePrereleaseVS = $excludePrereleaseVS } + } else { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unexpected value of -msbuildEngine: '$msbuildEngine'." + ExitWithExitCode 1 + } + + return $global:_BuildTool = $buildTool +} + +function GetDefaultMSBuildEngine() { + # Presence of tools.vs indicates the repo needs to build using VS msbuild on Windows. + if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { + return 'vs' + } + + if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') { + return 'dotnet' + } + + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "-msbuildEngine must be specified, or /global.json must specify 'tools.dotnet' or 'tools.vs'." + ExitWithExitCode 1 +} + +function GetNuGetPackageCachePath() { + if ($env:NUGET_PACKAGES -eq $null) { + # Use local cache on CI to ensure deterministic build. + # Avoid using the http cache as workaround for https://github.com/NuGet/Home/issues/3116 + # use global cache in dev builds to avoid cost of downloading packages. + # For directory normalization, see also: https://github.com/NuGet/Home/issues/7968 + if ($useGlobalNuGetCache) { + $env:NUGET_PACKAGES = Join-Path $env:UserProfile '.nuget\packages\' + } else { + $env:NUGET_PACKAGES = Join-Path $RepoRoot '.packages\' + $env:RESTORENOCACHE = $true + } + } + + return $env:NUGET_PACKAGES +} + +# Returns a full path to an Arcade SDK task project file. +function GetSdkTaskProject([string]$taskName) { + return Join-Path (Split-Path (InitializeToolset) -Parent) "SdkTasks\$taskName.proj" +} + +function InitializeNativeTools() { + if (-Not (Test-Path variable:DisableNativeToolsetInstalls) -And (Get-Member -InputObject $GlobalJson -Name "native-tools")) { + $nativeArgs= @{} + if ($ci) { + $nativeArgs = @{ + InstallDirectory = "$ToolsDir" + } + } + & "$PSScriptRoot/init-tools-native.ps1" @nativeArgs + } +} + +function InitializeToolset() { + if (Test-Path variable:global:_ToolsetBuildProj) { + return $global:_ToolsetBuildProj + } + + $nugetCache = GetNuGetPackageCachePath + + $toolsetVersion = $GlobalJson.'msbuild-sdks'.'Microsoft.DotNet.Arcade.Sdk' + $toolsetLocationFile = Join-Path $ToolsetDir "$toolsetVersion.txt" + + if (Test-Path $toolsetLocationFile) { + $path = Get-Content $toolsetLocationFile -TotalCount 1 + if (Test-Path $path) { + return $global:_ToolsetBuildProj = $path + } + } + + if (-not $restore) { + Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Toolset version $toolsetVersion has not been restored." + ExitWithExitCode 1 + } + + $buildTool = InitializeBuildTool + + $proj = Join-Path $ToolsetDir 'restore.proj' + $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'ToolsetRestore.binlog') } else { '' } + + '' | Set-Content $proj + + MSBuild-Core $proj $bl /t:__WriteToolsetLocation /clp:ErrorsOnly`;NoSummary /p:__ToolsetLocationOutputFile=$toolsetLocationFile + + $path = Get-Content $toolsetLocationFile -Encoding UTF8 -TotalCount 1 + if (!(Test-Path $path)) { + throw "Invalid toolset path: $path" + } + + return $global:_ToolsetBuildProj = $path +} + +function ExitWithExitCode([int] $exitCode) { + if ($ci -and $prepareMachine) { + Stop-Processes + } + exit $exitCode +} + +function Stop-Processes() { + Write-Host 'Killing running build processes...' + foreach ($processName in $processesToStopOnExit) { + Get-Process -Name $processName -ErrorAction SilentlyContinue | Stop-Process + } +} + +# +# Executes msbuild (or 'dotnet msbuild') with arguments passed to the function. +# The arguments are automatically quoted. +# Terminates the script if the build fails. +# +function MSBuild() { + if ($pipelinesLog) { + $buildTool = InitializeBuildTool + + if ($ci -and $buildTool.Tool -eq 'dotnet') { + $env:NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS = 20 + $env:NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS = 20 + Write-PipelineSetVariable -Name 'NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS' -Value '20' + Write-PipelineSetVariable -Name 'NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS' -Value '20' + } + + $toolsetBuildProject = InitializeToolset + $basePath = Split-Path -parent $toolsetBuildProject + $possiblePaths = @( + # new scripts need to work with old packages, so we need to look for the old names/versions + (Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.ArcadeLogging.dll')), + (Join-Path $basePath (Join-Path $buildTool.Framework 'Microsoft.DotNet.Arcade.Sdk.dll')), + (Join-Path $basePath (Join-Path netcoreapp2.1 'Microsoft.DotNet.ArcadeLogging.dll')), + (Join-Path $basePath (Join-Path netcoreapp2.1 'Microsoft.DotNet.Arcade.Sdk.dll')) + ) + $selectedPath = $null + foreach ($path in $possiblePaths) { + if (Test-Path $path -PathType Leaf) { + $selectedPath = $path + break + } + } + if (-not $selectedPath) { + Write-PipelineTelemetryError -Category 'Build' -Message 'Unable to find arcade sdk logger assembly.' + ExitWithExitCode 1 + } + $args += "/logger:$selectedPath" + } + + MSBuild-Core @args +} + +# +# Executes msbuild (or 'dotnet msbuild') with arguments passed to the function. +# The arguments are automatically quoted. +# Terminates the script if the build fails. +# +function MSBuild-Core() { + if ($ci) { + if (!$binaryLog -and !$excludeCIBinarylog) { + Write-PipelineTelemetryError -Category 'Build' -Message 'Binary log must be enabled in CI build, or explicitly opted-out from with the -excludeCIBinarylog switch.' + ExitWithExitCode 1 + } + + if ($nodeReuse) { + Write-PipelineTelemetryError -Category 'Build' -Message 'Node reuse must be disabled in CI build.' + ExitWithExitCode 1 + } + } + + $buildTool = InitializeBuildTool + + $cmdArgs = "$($buildTool.Command) /m /nologo /clp:Summary /v:$verbosity /nr:$nodeReuse /p:ContinuousIntegrationBuild=$ci" + + if ($warnAsError) { + $cmdArgs += ' /warnaserror /p:TreatWarningsAsErrors=true' + } + else { + $cmdArgs += ' /p:TreatWarningsAsErrors=false' + } + + foreach ($arg in $args) { + if ($arg -ne $null -and $arg.Trim() -ne "") { + $cmdArgs += " `"$arg`"" + } + } + + $env:ARCADE_BUILD_TOOL_COMMAND = "$($buildTool.Path) $cmdArgs" + + $exitCode = Exec-Process $buildTool.Path $cmdArgs + + if ($exitCode -ne 0) { + # We should not Write-PipelineTaskError here because that message shows up in the build summary + # The build already logged an error, that's the reason it failed. Producing an error here only adds noise. + Write-Host "Build failed with exit code $exitCode. Check errors above." -ForegroundColor Red + + $buildLog = GetMSBuildBinaryLogCommandLineArgument $args + if ($null -ne $buildLog) { + Write-Host "See log: $buildLog" -ForegroundColor DarkGray + } + + if ($ci) { + Write-PipelineSetResult -Result "Failed" -Message "msbuild execution failed." + # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error + # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error + ExitWithExitCode 0 + } else { + ExitWithExitCode $exitCode + } + } +} + +function GetMSBuildBinaryLogCommandLineArgument($arguments) { + foreach ($argument in $arguments) { + if ($argument -ne $null) { + $arg = $argument.Trim() + if ($arg.StartsWith('/bl:', "OrdinalIgnoreCase")) { + return $arg.Substring('/bl:'.Length) + } + + if ($arg.StartsWith('/binaryLogger:', 'OrdinalIgnoreCase')) { + return $arg.Substring('/binaryLogger:'.Length) + } + } + } + + return $null +} + +function GetExecutableFileName($baseName) { + if (IsWindowsPlatform) { + return "$baseName.exe" + } + else { + return $baseName + } +} + +function IsWindowsPlatform() { + return [environment]::OSVersion.Platform -eq [PlatformID]::Win32NT +} + +function Get-Darc($version) { + $darcPath = "$TempDir\darc\$(New-Guid)" + if ($version -ne $null) { + & $PSScriptRoot\darc-init.ps1 -toolpath $darcPath -darcVersion $version | Out-Host + } else { + & $PSScriptRoot\darc-init.ps1 -toolpath $darcPath | Out-Host + } + return "$darcPath\darc.exe" +} + +. $PSScriptRoot\pipeline-logging-functions.ps1 + +$RepoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..') +$EngRoot = Resolve-Path (Join-Path $PSScriptRoot '..') +$ArtifactsDir = Join-Path $RepoRoot 'artifacts' +$ToolsetDir = Join-Path $ArtifactsDir 'toolset' +$ToolsDir = Join-Path $RepoRoot '.tools' +$LogDir = Join-Path (Join-Path $ArtifactsDir 'log') $configuration +$TempDir = Join-Path (Join-Path $ArtifactsDir 'tmp') $configuration +$GlobalJson = Get-Content -Raw -Path (Join-Path $RepoRoot 'global.json') | ConvertFrom-Json +# true if global.json contains a "runtimes" section +$globalJsonHasRuntimes = if ($GlobalJson.tools.PSObject.Properties.Name -Match 'runtimes') { $true } else { $false } + +Create-Directory $ToolsetDir +Create-Directory $TempDir +Create-Directory $LogDir + +Write-PipelineSetVariable -Name 'Artifacts' -Value $ArtifactsDir +Write-PipelineSetVariable -Name 'Artifacts.Toolset' -Value $ToolsetDir +Write-PipelineSetVariable -Name 'Artifacts.Log' -Value $LogDir +Write-PipelineSetVariable -Name 'TEMP' -Value $TempDir +Write-PipelineSetVariable -Name 'TMP' -Value $TempDir + +# Import custom tools configuration, if present in the repo. +# Note: Import in global scope so that the script set top-level variables without qualification. +if (!$disableConfigureToolsetImport) { + $configureToolsetScript = Join-Path $EngRoot 'configure-toolset.ps1' + if (Test-Path $configureToolsetScript) { + . $configureToolsetScript + if ((Test-Path variable:failOnConfigureToolsetError) -And $failOnConfigureToolsetError) { + if ((Test-Path variable:LastExitCode) -And ($LastExitCode -ne 0)) { + Write-PipelineTelemetryError -Category 'Build' -Message 'configure-toolset.ps1 returned a non-zero exit code' + ExitWithExitCode $LastExitCode + } + } + } +} diff --git a/eng/common/tools.sh b/eng/common/tools.sh new file mode 100755 index 000000000..5fad1846e --- /dev/null +++ b/eng/common/tools.sh @@ -0,0 +1,534 @@ +#!/usr/bin/env bash + +# Initialize variables if they aren't already defined. + +# CI mode - set to true on CI server for PR validation build or official build. +ci=${ci:-false} + +# Set to true to use the pipelines logger which will enable Azure logging output. +# https://github.com/Microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md +# This flag is meant as a temporary opt-opt for the feature while validate it across +# our consumers. It will be deleted in the future. +if [[ "$ci" == true ]]; then + pipelines_log=${pipelines_log:-true} +else + pipelines_log=${pipelines_log:-false} +fi + +# Build configuration. Common values include 'Debug' and 'Release', but the repository may use other names. +configuration=${configuration:-'Debug'} + +# Set to true to opt out of outputting binary log while running in CI +exclude_ci_binary_log=${exclude_ci_binary_log:-false} + +if [[ "$ci" == true && "$exclude_ci_binary_log" == false ]]; then + binary_log_default=true +else + binary_log_default=false +fi + +# Set to true to output binary log from msbuild. Note that emitting binary log slows down the build. +binary_log=${binary_log:-$binary_log_default} + +# Turns on machine preparation/clean up code that changes the machine state (e.g. kills build processes). +prepare_machine=${prepare_machine:-false} + +# True to restore toolsets and dependencies. +restore=${restore:-true} + +# Adjusts msbuild verbosity level. +verbosity=${verbosity:-'minimal'} + +# Set to true to reuse msbuild nodes. Recommended to not reuse on CI. +if [[ "$ci" == true ]]; then + node_reuse=${node_reuse:-false} +else + node_reuse=${node_reuse:-true} +fi + +# Configures warning treatment in msbuild. +warn_as_error=${warn_as_error:-true} + +# True to attempt using .NET Core already that meets requirements specified in global.json +# installed on the machine instead of downloading one. +use_installed_dotnet_cli=${use_installed_dotnet_cli:-true} + +# Enable repos to use a particular version of the on-line dotnet-install scripts. +# default URL: https://dot.net/v1/dotnet-install.sh +dotnetInstallScriptVersion=${dotnetInstallScriptVersion:-'v1'} + +# True to use global NuGet cache instead of restoring packages to repository-local directory. +if [[ "$ci" == true ]]; then + use_global_nuget_cache=${use_global_nuget_cache:-false} +else + use_global_nuget_cache=${use_global_nuget_cache:-true} +fi + +# Used when restoring .NET SDK from alternative feeds +runtime_source_feed=${runtime_source_feed:-''} +runtime_source_feed_key=${runtime_source_feed_key:-''} + +# Resolve any symlinks in the given path. +function ResolvePath { + local path=$1 + + while [[ -h $path ]]; do + local dir="$( cd -P "$( dirname "$path" )" && pwd )" + path="$(readlink "$path")" + + # if $path was a relative symlink, we need to resolve it relative to the path where the + # symlink file was located + [[ $path != /* ]] && path="$dir/$path" + done + + # return value + _ResolvePath="$path" +} + +# ReadVersionFromJson [json key] +function ReadGlobalVersion { + local key=$1 + + if command -v jq &> /dev/null; then + _ReadGlobalVersion="$(jq -r ".[] | select(has(\"$key\")) | .\"$key\"" "$global_json_file")" + elif [[ "$(cat "$global_json_file")" =~ \"$key\"[[:space:]\:]*\"([^\"]+) ]]; then + _ReadGlobalVersion=${BASH_REMATCH[1]} + fi + + if [[ -z "$_ReadGlobalVersion" ]]; then + Write-PipelineTelemetryError -category 'Build' "Error: Cannot find \"$key\" in $global_json_file" + ExitWithExitCode 1 + fi +} + +function InitializeDotNetCli { + if [[ -n "${_InitializeDotNetCli:-}" ]]; then + return + fi + + local install=$1 + + # Don't resolve runtime, shared framework, or SDK from other locations to ensure build determinism + export DOTNET_MULTILEVEL_LOOKUP=0 + + # Disable first run since we want to control all package sources + export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + + # Disable telemetry on CI + if [[ $ci == true ]]; then + export DOTNET_CLI_TELEMETRY_OPTOUT=1 + fi + + # LTTNG is the logging infrastructure used by Core CLR. Need this variable set + # so it doesn't output warnings to the console. + export LTTNG_HOME="$HOME" + + # Source Build uses DotNetCoreSdkDir variable + if [[ -n "${DotNetCoreSdkDir:-}" ]]; then + export DOTNET_INSTALL_DIR="$DotNetCoreSdkDir" + fi + + # Find the first path on $PATH that contains the dotnet.exe + if [[ "$use_installed_dotnet_cli" == true && $global_json_has_runtimes == false && -z "${DOTNET_INSTALL_DIR:-}" ]]; then + local dotnet_path=`command -v dotnet` + if [[ -n "$dotnet_path" ]]; then + ResolvePath "$dotnet_path" + export DOTNET_INSTALL_DIR=`dirname "$_ResolvePath"` + fi + fi + + ReadGlobalVersion "dotnet" + local dotnet_sdk_version=$_ReadGlobalVersion + local dotnet_root="" + + # Use dotnet installation specified in DOTNET_INSTALL_DIR if it contains the required SDK version, + # otherwise install the dotnet CLI and SDK to repo local .dotnet directory to avoid potential permission issues. + if [[ $global_json_has_runtimes == false && -n "${DOTNET_INSTALL_DIR:-}" && -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then + dotnet_root="$DOTNET_INSTALL_DIR" + else + dotnet_root="$repo_root/.dotnet" + + export DOTNET_INSTALL_DIR="$dotnet_root" + + if [[ ! -d "$DOTNET_INSTALL_DIR/sdk/$dotnet_sdk_version" ]]; then + if [[ "$install" == true ]]; then + InstallDotNetSdk "$dotnet_root" "$dotnet_sdk_version" + else + Write-PipelineTelemetryError -category 'InitializeToolset' "Unable to find dotnet with SDK version '$dotnet_sdk_version'" + ExitWithExitCode 1 + fi + fi + fi + + # Add dotnet to PATH. This prevents any bare invocation of dotnet in custom + # build steps from using anything other than what we've downloaded. + Write-PipelinePrependPath -path "$dotnet_root" + + Write-PipelineSetVariable -name "DOTNET_MULTILEVEL_LOOKUP" -value "0" + Write-PipelineSetVariable -name "DOTNET_SKIP_FIRST_TIME_EXPERIENCE" -value "1" + + # return value + _InitializeDotNetCli="$dotnet_root" +} + +function InstallDotNetSdk { + local root=$1 + local version=$2 + local architecture="unset" + if [[ $# -ge 3 ]]; then + architecture=$3 + fi + InstallDotNet "$root" "$version" $architecture 'sdk' 'false' $runtime_source_feed $runtime_source_feed_key +} + +function InstallDotNet { + local root=$1 + local version=$2 + + GetDotNetInstallScript "$root" + local install_script=$_GetDotNetInstallScript + + local archArg='' + if [[ -n "${3:-}" ]] && [ "$3" != 'unset' ]; then + archArg="--architecture $3" + fi + local runtimeArg='' + if [[ -n "${4:-}" ]] && [ "$4" != 'sdk' ]; then + runtimeArg="--runtime $4" + fi + local skipNonVersionedFilesArg="" + if [[ "$#" -ge "5" ]] && [[ "$5" != 'false' ]]; then + skipNonVersionedFilesArg="--skip-non-versioned-files" + fi + bash "$install_script" --version $version --install-dir "$root" $archArg $runtimeArg $skipNonVersionedFilesArg || { + local exit_code=$? + echo "Failed to install dotnet SDK from public location (exit code '$exit_code')." + + local runtimeSourceFeed='' + if [[ -n "${6:-}" ]]; then + runtimeSourceFeed="--azure-feed $6" + fi + + local runtimeSourceFeedKey='' + if [[ -n "${7:-}" ]]; then + # The 'base64' binary on alpine uses '-d' and doesn't support '--decode' + # '-d'. To work around this, do a simple detection and switch the parameter + # accordingly. + decodeArg="--decode" + if base64 --help 2>&1 | grep -q "BusyBox"; then + decodeArg="-d" + fi + decodedFeedKey=`echo $7 | base64 $decodeArg` + runtimeSourceFeedKey="--feed-credential $decodedFeedKey" + fi + + if [[ -n "$runtimeSourceFeed" || -n "$runtimeSourceFeedKey" ]]; then + bash "$install_script" --version $version --install-dir "$root" $archArg $runtimeArg $skipNonVersionedFilesArg $runtimeSourceFeed $runtimeSourceFeedKey || { + local exit_code=$? + Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to install dotnet SDK from custom location '$runtimeSourceFeed' (exit code '$exit_code')." + ExitWithExitCode $exit_code + } + else + if [[ $exit_code != 0 ]]; then + Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to install dotnet SDK from public location (exit code '$exit_code')." + fi + ExitWithExitCode $exit_code + fi + } +} + +function with_retries { + local maxRetries=5 + local retries=1 + echo "Trying to run '$@' for maximum of $maxRetries attempts." + while [[ $((retries++)) -le $maxRetries ]]; do + "$@" + + if [[ $? == 0 ]]; then + echo "Ran '$@' successfully." + return 0 + fi + + timeout=$((3**$retries-1)) + echo "Failed to execute '$@'. Waiting $timeout seconds before next attempt ($retries out of $maxRetries)." 1>&2 + sleep $timeout + done + + echo "Failed to execute '$@' for $maxRetries times." 1>&2 + + return 1 +} + +function GetDotNetInstallScript { + local root=$1 + local install_script="$root/dotnet-install.sh" + local install_script_url="https://dot.net/$dotnetInstallScriptVersion/dotnet-install.sh" + + if [[ ! -a "$install_script" ]]; then + mkdir -p "$root" + + echo "Downloading '$install_script_url'" + + # Use curl if available, otherwise use wget + if command -v curl > /dev/null; then + # first, try directly, if this fails we will retry with verbose logging + curl "$install_script_url" -sSL --retry 10 --create-dirs -o "$install_script" || { + if command -v openssl &> /dev/null; then + echo "Curl failed; dumping some information about dotnet.microsoft.com for later investigation" + echo | openssl s_client -showcerts -servername dotnet.microsoft.com -connect dotnet.microsoft.com:443 + fi + echo "Will now retry the same URL with verbose logging." + with_retries curl "$install_script_url" -sSL --verbose --retry 10 --create-dirs -o "$install_script" || { + local exit_code=$? + Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to acquire dotnet install script (exit code '$exit_code')." + ExitWithExitCode $exit_code + } + } + else + with_retries wget -v -O "$install_script" "$install_script_url" || { + local exit_code=$? + Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to acquire dotnet install script (exit code '$exit_code')." + ExitWithExitCode $exit_code + } + fi + fi + # return value + _GetDotNetInstallScript="$install_script" +} + +function InitializeBuildTool { + if [[ -n "${_InitializeBuildTool:-}" ]]; then + return + fi + + InitializeDotNetCli $restore + + # return values + _InitializeBuildTool="$_InitializeDotNetCli/dotnet" + _InitializeBuildToolCommand="msbuild" + _InitializeBuildToolFramework="netcoreapp3.1" +} + +# Set RestoreNoCache as a workaround for https://github.com/NuGet/Home/issues/3116 +function GetNuGetPackageCachePath { + if [[ -z ${NUGET_PACKAGES:-} ]]; then + if [[ "$use_global_nuget_cache" == true ]]; then + export NUGET_PACKAGES="$HOME/.nuget/packages" + else + export NUGET_PACKAGES="$repo_root/.packages" + export RESTORENOCACHE=true + fi + fi + + # return value + _GetNuGetPackageCachePath=$NUGET_PACKAGES +} + +function InitializeNativeTools() { + if [[ -n "${DisableNativeToolsetInstalls:-}" ]]; then + return + fi + if grep -Fq "native-tools" $global_json_file + then + local nativeArgs="" + if [[ "$ci" == true ]]; then + nativeArgs="--installDirectory $tools_dir" + fi + "$_script_dir/init-tools-native.sh" $nativeArgs + fi +} + +function InitializeToolset { + if [[ -n "${_InitializeToolset:-}" ]]; then + return + fi + + GetNuGetPackageCachePath + + ReadGlobalVersion "Microsoft.DotNet.Arcade.Sdk" + + local toolset_version=$_ReadGlobalVersion + local toolset_location_file="$toolset_dir/$toolset_version.txt" + + if [[ -a "$toolset_location_file" ]]; then + local path=`cat "$toolset_location_file"` + if [[ -a "$path" ]]; then + # return value + _InitializeToolset="$path" + return + fi + fi + + if [[ "$restore" != true ]]; then + Write-PipelineTelemetryError -category 'InitializeToolset' "Toolset version $toolset_version has not been restored." + ExitWithExitCode 2 + fi + + local proj="$toolset_dir/restore.proj" + + local bl="" + if [[ "$binary_log" == true ]]; then + bl="/bl:$log_dir/ToolsetRestore.binlog" + fi + + echo '' > "$proj" + MSBuild-Core "$proj" $bl /t:__WriteToolsetLocation /clp:ErrorsOnly\;NoSummary /p:__ToolsetLocationOutputFile="$toolset_location_file" + + local toolset_build_proj=`cat "$toolset_location_file"` + + if [[ ! -a "$toolset_build_proj" ]]; then + Write-PipelineTelemetryError -category 'Build' "Invalid toolset path: $toolset_build_proj" + ExitWithExitCode 3 + fi + + # return value + _InitializeToolset="$toolset_build_proj" +} + +function ExitWithExitCode { + if [[ "$ci" == true && "$prepare_machine" == true ]]; then + StopProcesses + fi + exit $1 +} + +function StopProcesses { + echo "Killing running build processes..." + pkill -9 "dotnet" || true + pkill -9 "vbcscompiler" || true + return 0 +} + +function MSBuild { + local args=$@ + if [[ "$pipelines_log" == true ]]; then + InitializeBuildTool + InitializeToolset + + if [[ "$ci" == true ]]; then + export NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS=20 + export NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS=20 + Write-PipelineSetVariable -name "NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS" -value "20" + Write-PipelineSetVariable -name "NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS" -value "20" + fi + + local toolset_dir="${_InitializeToolset%/*}" + # new scripts need to work with old packages, so we need to look for the old names/versions + local selectedPath= + local possiblePaths=() + possiblePaths+=( "$toolset_dir/$_InitializeBuildToolFramework/Microsoft.DotNet.ArcadeLogging.dll" ) + possiblePaths+=( "$toolset_dir/$_InitializeBuildToolFramework/Microsoft.DotNet.Arcade.Sdk.dll" ) + possiblePaths+=( "$toolset_dir/netcoreapp2.1/Microsoft.DotNet.ArcadeLogging.dll" ) + possiblePaths+=( "$toolset_dir/netcoreapp2.1/Microsoft.DotNet.Arcade.Sdk.dll" ) + for path in "${possiblePaths[@]}"; do + if [[ -f $path ]]; then + selectedPath=$path + break + fi + done + if [[ -z "$selectedPath" ]]; then + Write-PipelineTelemetryError -category 'Build' "Unable to find arcade sdk logger assembly." + ExitWithExitCode 1 + fi + args+=( "-logger:$selectedPath" ) + fi + + MSBuild-Core ${args[@]} +} + +function MSBuild-Core { + if [[ "$ci" == true ]]; then + if [[ "$binary_log" != true && "$exclude_ci_binary_log" != true ]]; then + Write-PipelineTelemetryError -category 'Build' "Binary log must be enabled in CI build, or explicitly opted-out from with the -noBinaryLog switch." + ExitWithExitCode 1 + fi + + if [[ "$node_reuse" == true ]]; then + Write-PipelineTelemetryError -category 'Build' "Node reuse must be disabled in CI build." + ExitWithExitCode 1 + fi + fi + + InitializeBuildTool + + local warnaserror_switch="" + if [[ $warn_as_error == true ]]; then + warnaserror_switch="/warnaserror" + fi + + function RunBuildTool { + export ARCADE_BUILD_TOOL_COMMAND="$_InitializeBuildTool $@" + + "$_InitializeBuildTool" "$@" || { + local exit_code=$? + # We should not Write-PipelineTaskError here because that message shows up in the build summary + # The build already logged an error, that's the reason it failed. Producing an error here only adds noise. + echo "Build failed with exit code $exit_code. Check errors above." + if [[ "$ci" == "true" ]]; then + Write-PipelineSetResult -result "Failed" -message "msbuild execution failed." + # Exiting with an exit code causes the azure pipelines task to log yet another "noise" error + # The above Write-PipelineSetResult will cause the task to be marked as failure without adding yet another error + ExitWithExitCode 0 + else + ExitWithExitCode $exit_code + fi + } + } + + RunBuildTool "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@" +} + +ResolvePath "${BASH_SOURCE[0]}" +_script_dir=`dirname "$_ResolvePath"` + +. "$_script_dir/pipeline-logging-functions.sh" + +eng_root=`cd -P "$_script_dir/.." && pwd` +repo_root=`cd -P "$_script_dir/../.." && pwd` +artifacts_dir="$repo_root/artifacts" +toolset_dir="$artifacts_dir/toolset" +tools_dir="$repo_root/.tools" +log_dir="$artifacts_dir/log/$configuration" +temp_dir="$artifacts_dir/tmp/$configuration" + +global_json_file="$repo_root/global.json" +# determine if global.json contains a "runtimes" entry +global_json_has_runtimes=false +if command -v jq &> /dev/null; then + if jq -er '. | select(has("runtimes"))' "$global_json_file" &> /dev/null; then + global_json_has_runtimes=true + fi +elif [[ "$(cat "$global_json_file")" =~ \"runtimes\"[[:space:]\:]*\{ ]]; then + global_json_has_runtimes=true +fi + +# HOME may not be defined in some scenarios, but it is required by NuGet +if [[ -z $HOME ]]; then + export HOME="$repo_root/artifacts/.home/" + mkdir -p "$HOME" +fi + +mkdir -p "$toolset_dir" +mkdir -p "$temp_dir" +mkdir -p "$log_dir" + +Write-PipelineSetVariable -name "Artifacts" -value "$artifacts_dir" +Write-PipelineSetVariable -name "Artifacts.Toolset" -value "$toolset_dir" +Write-PipelineSetVariable -name "Artifacts.Log" -value "$log_dir" +Write-PipelineSetVariable -name "Temp" -value "$temp_dir" +Write-PipelineSetVariable -name "TMP" -value "$temp_dir" + +# Import custom tools configuration, if present in the repo. +if [ -z "${disable_configure_toolset_import:-}" ]; then + configure_toolset_script="$eng_root/configure-toolset.sh" + if [[ -a "$configure_toolset_script" ]]; then + . "$configure_toolset_script" + fi +fi + +# TODO: https://github.com/dotnet/arcade/issues/1468 +# Temporary workaround to avoid breaking change. +# Remove once repos are updated. +if [[ -n "${useInstalledDotNetCli:-}" ]]; then + use_installed_dotnet_cli="$useInstalledDotNetCli" +fi diff --git a/eng/restore-toolset.ps1 b/eng/restore-toolset.ps1 new file mode 100644 index 000000000..69e507180 --- /dev/null +++ b/eng/restore-toolset.ps1 @@ -0,0 +1,39 @@ +function InitializeCustomSDKToolset { + if (-not $restore) { + return + } + + # Turn off MSBuild Node re-use + $env:MSBUILDDISABLENODEREUSE=1 + + # Workaround for the sockets issue when restoring with many nuget feeds. + $env:DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER=0 + + # Enable vs test console logging + $env:VSTEST_BUILD_TRACE=1 + $env:VSTEST_TRACE_BUILD=1 + + $env:DOTNET_CLI_TELEMETRY_PROFILE='$env:DOTNET_CLI_TELEMETRY_PROFILE;https://github.com/dotnet/roslyn-sdk' + + $cli = InitializeDotnetCli -install:$true + InstallDotNetSharedFramework "1.1.12" + InstallDotNetSharedFramework "2.0.9" + InstallDotNetSharedFramework "2.1.10" + InstallDotNetSharedFramework "2.2.5" +} + +function InstallDotNetSharedFramework([string]$version) { + $dotnetRoot = $env:DOTNET_INSTALL_DIR + $fxDir = Join-Path $dotnetRoot "shared\Microsoft.NETCore.App\$version" + + if (!(Test-Path $fxDir)) { + $installScript = GetDotNetInstallScript $dotnetRoot + & $installScript -Version $version -InstallDir $dotnetRoot -Runtime "dotnet" + + if($lastExitCode -ne 0) { + Write-Output "Failed to install Shared Framework $version. Ignoring failure as not all distros carrie all versions of the framework." + } + } +} + +InitializeCustomSDKToolset \ No newline at end of file diff --git a/global.json b/global.json new file mode 100644 index 000000000..382a76cc4 --- /dev/null +++ b/global.json @@ -0,0 +1,22 @@ +{ + "sdk": { + "version": "6.0.100-preview.3.21202.5", + "allowPrerelease": true, + "rollForward": "major" + }, + "tools": { + "dotnet": "6.0.100-preview.3.21202.5", + "runtimes": { + "dotnet": [ + "3.1.0" + ] + }, + "vs": { + "version": "16.8" + }, + "xcopy-msbuild": "16.8.0-preview2.1" + }, + "msbuild-sdks": { + "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.21304.1" + } +} diff --git a/netci.groovy b/netci.groovy deleted file mode 100644 index 112734f54..000000000 --- a/netci.groovy +++ /dev/null @@ -1,60 +0,0 @@ -// Groovy Script: http://www.groovy-lang.org/syntax.html -// Jenkins DSL: https://github.com/jenkinsci/job-dsl-plugin/wiki - -import jobs.generation.Utilities; -import jobs.generation.InternalUtilities; -import jobs.generation.ArchivalSettings; - -static addArchival(def job, def configName) { - def archivalSettings = new ArchivalSettings() - archivalSettings.addFiles("**/artifacts/**") - archivalSettings.excludeFiles("**/artifacts/${configName}/obj/**") - archivalSettings.excludeFiles("**/artifacts/${configName}/tmp/**") - archivalSettings.excludeFiles("**/artifacts/${configName}/VSSetup.obj/**") - archivalSettings.setFailIfNothingArchived() - archivalSettings.setArchiveOnFailure() - - Utilities.addArchival(job, archivalSettings) -} - -static addGithubTrigger(def job, def isPR, def branchName, def jobName) { - if (isPR) { - def prContext = "prtest/${jobName.replace('_', '/')}" - def triggerPhrase = "(?i)^\\s*(@?dotnet-bot\\s+)?(re)?test\\s+(${prContext})(\\s+please)?\\s*\$" - def triggerOnPhraseOnly = false - - Utilities.addGithubPRTriggerForBranch(job, branchName, prContext, triggerPhrase, triggerOnPhraseOnly) - } else { - Utilities.addGithubPushTrigger(job) - } -} - -def createJob(def platform, def configName, def isPR) { - def projectName = GithubProject - def branchName = GithubBranchName - def jobName = "${platform}_${configName}" - def newJob = job(InternalUtilities.getFullJobName(projectName, jobName, isPR)) - - InternalUtilities.standardJobSetup(newJob, projectName, isPR, "*/${branchName}") - - addGithubTrigger(newJob, isPR, branchName, jobName) - addArchival(newJob, configName) - - return newJob -} - -[true, false].each { isPR -> - ['windows'].each { platform -> - ['debug', 'release'].each { configName -> - def newJob = createJob(platform, configName, isPR) - - Utilities.setMachineAffinity(newJob, 'Windows_NT', 'latest-dev15-3') - - newJob.with { - steps { - batchFile(".\\build\\CIBuild.cmd -configuration ${configName} -prepareMachine") - } - } - } - } -} \ No newline at end of file diff --git a/restore.cmd b/restore.cmd index aa70f2cb0..bd639ea10 100644 --- a/restore.cmd +++ b/restore.cmd @@ -1,3 +1,3 @@ @echo off -powershell -ExecutionPolicy ByPass %~dp0build\Build.ps1 -restore %* +powershell -NoProfile -ExecutionPolicy ByPass -command "& """%~dp0eng\common\Build.ps1""" -restore %*" exit /b %ErrorLevel% \ No newline at end of file diff --git a/samples/.editorconfig b/samples/.editorconfig new file mode 100644 index 000000000..2f9edc737 --- /dev/null +++ b/samples/.editorconfig @@ -0,0 +1,31 @@ + +# Avoid "this." and "Me." if not necessary +dotnet_style_qualification_for_field = true:error +dotnet_style_qualification_for_property = true:error +dotnet_style_qualification_for_method = true:error +dotnet_style_qualification_for_event = true:error + +# Use language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:error +dotnet_style_predefined_type_for_member_access = true:error + +# Suggest more modern language features when available +dotnet_style_object_initializer = true:error +dotnet_style_collection_initializer = true:error +dotnet_style_coalesce_expression = true:error +dotnet_style_null_propagation = true:error +dotnet_style_explicit_tuple_names = true:error + +# CSharp code style settings: +[*.cs] +# Prefer "var" everywhere +csharp_style_var_for_built_in_types = false:error +csharp_style_var_when_type_is_apparent = false:error +csharp_style_var_elsewhere = false:error + +# Suggest more modern language features when available +csharp_style_pattern_matching_over_is_with_cast_check = true:error +csharp_style_pattern_matching_over_as_with_null_check = true:error +csharp_style_inlined_variable_declaration = true:error +csharp_style_throw_expression = true:error +csharp_style_conditional_delegate_call = true:error diff --git a/samples/CSharp/APISamples/APISamples.CSharp.UnitTests.csproj b/samples/CSharp/APISamples/APISamples.CSharp.UnitTests.csproj new file mode 100644 index 000000000..ac0136e72 --- /dev/null +++ b/samples/CSharp/APISamples/APISamples.CSharp.UnitTests.csproj @@ -0,0 +1,10 @@ + + + + netcoreapp2.0 + + + + + + diff --git a/samples/CSharp/APISamples/Compilations.cs b/samples/CSharp/APISamples/Compilations.cs new file mode 100644 index 000000000..e9c968c8e --- /dev/null +++ b/samples/CSharp/APISamples/Compilations.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; +using Xunit; + +namespace APISamples +{ + public class Compilations + { + [Fact] + public void EndToEndCompileAndRun() + { + string expression = "6 * 7"; + string text = @"public class Calculator +{ + public static object Evaluate() + { + return $; + } +}".Replace("$", expression); + + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(text); + CSharpCompilation compilation = CSharpCompilation.Create( + "calc.dll", + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary), + syntaxTrees: new[] { tree }, + references: new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }); + + Assembly compiledAssembly; + using (MemoryStream stream = new MemoryStream()) + { + Microsoft.CodeAnalysis.Emit.EmitResult compileResult = compilation.Emit(stream); + compiledAssembly = Assembly.Load(stream.GetBuffer()); + } + + Type calculator = compiledAssembly.GetType("Calculator"); + MethodInfo evaluate = calculator.GetMethod("Evaluate"); + string answer = evaluate.Invoke(null, null).ToString(); + + Assert.Equal("42", answer); + } + + [Fact] + public void GetErrorsAndWarnings() + { + string text = @"class Program +{ + static int Main(string[] args) + { + } +}"; + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(text); + CSharpCompilation compilation = CSharpCompilation + .Create("program.exe") + .AddSyntaxTrees(tree) + .AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location)); + IEnumerable errorsAndWarnings = compilation.GetDiagnostics(); + Assert.Single(errorsAndWarnings); + Diagnostic error = errorsAndWarnings.First(); + Assert.Equal( + "'Program.Main(string[])': not all code paths return a value", + error.GetMessage(CultureInfo.InvariantCulture)); + Location errorLocation = error.Location; + Assert.Equal(4, error.Location.SourceSpan.Length); + SourceText programText = errorLocation.SourceTree.GetText(); + Assert.Equal("Main", programText.ToString(errorLocation.SourceSpan)); + FileLinePositionSpan span = error.Location.GetLineSpan(); + Assert.Equal(15, span.StartLinePosition.Character); + Assert.Equal(2, span.StartLinePosition.Line); + } + } +} diff --git a/samples/CSharp/APISamples/FAQ.cs b/samples/CSharp/APISamples/FAQ.cs new file mode 100644 index 000000000..f65f525d2 --- /dev/null +++ b/samples/CSharp/APISamples/FAQ.cs @@ -0,0 +1,2382 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Text; +using Xunit; + +namespace APISamples +{ + public class FAQ + { + [AttributeUsage(AttributeTargets.Method)] + private class FAQAttribute : Attribute + { + public int Id { get; private set; } + + public FAQAttribute(int id) + { + Id = id; + } + } + + private MetadataReference mscorlib; + + private MetadataReference Mscorlib + { + get + { + if (mscorlib == null) + { + mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); + } + + return mscorlib; + } + } + + #region Section 1 : Getting Information Questions + [FAQ(1)] + [Fact] + public void GetTypeForIdentifier() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +class Program +{ + public static void Main() + { + var i = 0; i += 1; + } +}"); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation", + syntaxTrees: new[] { tree }, references: new[] { Mscorlib }); + SemanticModel model = compilation.GetSemanticModel(tree); + + // Get IdentifierNameSyntax corresponding to the identifier 'var' above. + IdentifierNameSyntax identifier = tree.GetRoot() + .DescendantNodes().OfType() + .Single(i => i.IsVar); + + // Use GetTypeInfo() to get TypeSymbol corresponding to the identifier 'var' above. + ITypeSymbol type = model.GetTypeInfo(identifier).Type; + Assert.Equal(SpecialType.System_Int32, type.SpecialType); + Assert.Equal("int", type.ToDisplayString()); + + // Alternately, use GetSymbolInfo() to get TypeSymbol corresponding to identifier 'var' above. + type = (ITypeSymbol)model.GetSymbolInfo(identifier).Symbol; + Assert.Equal(SpecialType.System_Int32, type.SpecialType); + Assert.Equal("int", type.ToDisplayString()); + } + + [FAQ(2)] + [Fact] + public void GetTypeForVariableDeclaration() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +class Program +{ + public static void Main() + { + var i = 0; i += 1; + } +}"); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation") + .AddReferences(Mscorlib) + .AddSyntaxTrees(tree); + SemanticModel model = compilation.GetSemanticModel(tree); + + // Get VariableDeclaratorSyntax corresponding to the statement 'var i = ...' above. + VariableDeclaratorSyntax variableDeclarator = tree.GetRoot() + .DescendantNodes().OfType() + .Single(); + + // Get TypeSymbol corresponding to 'var i' above. + ITypeSymbol type = ((ILocalSymbol)model.GetDeclaredSymbol(variableDeclarator)).Type; + Assert.Equal(SpecialType.System_Int32, type.SpecialType); + Assert.Equal("int", type.ToDisplayString()); + } + + [FAQ(3)] + [Fact] + public void GetTypeForExpressions() + { + string source = @" +using System; +class Program +{ + public void M(short[] s) + { + var d = 1.0; + Console.WriteLine(s[0] + d); + } + public static void Main() + { + } +}"; + ProjectId projectId = ProjectId.CreateNewId(); + DocumentId documentId = DocumentId.CreateNewId(projectId); + + Solution solution = new AdhocWorkspace().CurrentSolution + .AddProject(projectId, "MyProject", "MyProject", LanguageNames.CSharp) + .AddMetadataReference(projectId, Mscorlib) + .AddDocument(documentId, "MyFile.cs", source); + Document document = solution.GetDocument(documentId); + SemanticModel model = (SemanticModel)document.GetSemanticModelAsync().Result; + + // Get BinaryExpressionSyntax corresponding to the expression 's[0] + d' above. + BinaryExpressionSyntax addExpression = document.GetSyntaxRootAsync().Result + .DescendantNodes().OfType().Single(); + + // Get TypeSymbol corresponding to expression 's[0] + d' above. + TypeInfo expressionTypeInfo = model.GetTypeInfo(addExpression); + ITypeSymbol expressionType = expressionTypeInfo.Type; + Assert.Equal(SpecialType.System_Double, expressionType.SpecialType); + Assert.Equal("double", expressionType.ToDisplayString()); + Assert.Equal(SpecialType.System_Double, expressionTypeInfo.ConvertedType.SpecialType); + + Conversion conversion = model.GetConversion(addExpression); + Assert.True(conversion.IsIdentity); + + // Get IdentifierNameSyntax corresponding to the variable 'd' in expression 's[0] + d' above. + IdentifierNameSyntax identifier = (IdentifierNameSyntax)addExpression.Right; + + // Use GetTypeInfo() to get TypeSymbol corresponding to variable 'd' above. + TypeInfo variableTypeInfo = model.GetTypeInfo(identifier); + ITypeSymbol variableType = variableTypeInfo.Type; + Assert.Equal(SpecialType.System_Double, variableType.SpecialType); + Assert.Equal("double", variableType.ToDisplayString()); + Assert.Equal(SpecialType.System_Double, variableTypeInfo.ConvertedType.SpecialType); + + conversion = model.GetConversion(identifier); + Assert.True(conversion.IsIdentity); + + // Alternately, use GetSymbolInfo() to get TypeSymbol corresponding to variable 'd' above. + variableType = ((ILocalSymbol)model.GetSymbolInfo(identifier).Symbol).Type; + Assert.Equal(SpecialType.System_Double, variableType.SpecialType); + Assert.Equal("double", variableType.ToDisplayString()); + + // Get ElementAccessExpressionSyntax corresponding to 's[0]' in expression 's[0] + d' above. + ElementAccessExpressionSyntax elementAccess = (ElementAccessExpressionSyntax)addExpression.Left; + + // Use GetTypeInfo() to get TypeSymbol corresponding to 's[0]' above. + expressionTypeInfo = model.GetTypeInfo(elementAccess); + expressionType = expressionTypeInfo.Type; + Assert.Equal(SpecialType.System_Int16, expressionType.SpecialType); + Assert.Equal("short", expressionType.ToDisplayString()); + Assert.Equal(SpecialType.System_Double, expressionTypeInfo.ConvertedType.SpecialType); + + conversion = model.GetConversion(elementAccess); + Assert.True(conversion.IsImplicit && conversion.IsNumeric); + + // Get IdentifierNameSyntax corresponding to the parameter 's' in expression 's[0] + d' above. + identifier = (IdentifierNameSyntax)elementAccess.Expression; + + // Use GetTypeInfo() to get TypeSymbol corresponding to parameter 's' above. + variableTypeInfo = model.GetTypeInfo(identifier); + variableType = variableTypeInfo.Type; + Assert.Equal("short[]", variableType.ToDisplayString()); + Assert.Equal("short[]", variableTypeInfo.ConvertedType.ToDisplayString()); + + conversion = model.GetConversion(identifier); + Assert.True(conversion.IsIdentity); + + // Alternately, use GetSymbolInfo() to get TypeSymbol corresponding to parameter 's' above. + variableType = ((IParameterSymbol)model.GetSymbolInfo(identifier).Symbol).Type; + Assert.Equal("short[]", variableType.ToDisplayString()); + Assert.Equal(SpecialType.System_Int16, ((IArrayTypeSymbol)variableType).ElementType.SpecialType); + } + + [FAQ(4)] + [Fact(Skip = "Need to load correct assembly references now that this is a .NET core app")] + public void GetInScopeSymbols() + { + string source = @" +class C +{ +} +class Program +{ + private static int i = 0; + public static void Main() + { + int j = 0; j += i; + + // What symbols are in scope here? + } +}"; + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(source); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation", + syntaxTrees: new[] { tree }, references: new[] { Mscorlib }); + SemanticModel model = compilation.GetSemanticModel(tree); + + // Get position of the comment above. + int position = source.IndexOf("//"); + + // Get 'all' symbols that are in scope at the above position. + System.Collections.Immutable.ImmutableArray symbols = model.LookupSymbols(position); + + // Note: "Windows" only appears as a symbol at this location in Windows 8.1. + string results = string.Join("\r\n", symbols.Select(symbol => symbol.ToDisplayString()).Where(s => s != "Windows").OrderBy(s => s)); + + Assert.Equal(@"C +j +Microsoft +object.~Object() +object.Equals(object) +object.Equals(object, object) +object.GetHashCode() +object.GetType() +object.MemberwiseClone() +object.ReferenceEquals(object, object) +object.ToString() +Program +Program.i +Program.Main() +System", results); + + // Filter results - get everything except instance members. + symbols = model.LookupStaticMembers(position); + + // Note: "Windows" only appears as a symbol at this location in Windows 8.1. + results = string.Join("\r\n", symbols.Select(symbol => symbol.ToDisplayString()).Where(s => s != "Windows").OrderBy(s => s)); + + Assert.Equal(@"C +j +Microsoft +object.Equals(object, object) +object.ReferenceEquals(object, object) +Program +Program.i +Program.Main() +System", results); + + // Filter results by looking at Kind of returned symbols (only get locals and fields). + results = string.Join("\r\n", symbols + .Where(symbol => symbol.Kind == SymbolKind.Local || symbol.Kind == SymbolKind.Field) + .Select(symbol => symbol.ToDisplayString()).OrderBy(s => s)); + + Assert.Equal(@"j +Program.i", results); + } + + [FAQ(5)] + [Fact] + public void GetSymbolsForAccessibleMembersOfAType() + { + string source = @" +using System; +public class C +{ + internal int InstanceField = 0; + public int InstanceProperty { get; set; } + internal void InstanceMethod() + { + Console.WriteLine(InstanceField); + } + protected void InaccessibleInstanceMethod() + { + Console.WriteLine(InstanceProperty); + } +} +public static class ExtensionMethods +{ + public static void ExtensionMethod(this C s) + { + } +} +class Program +{ + static void Main() + { + C c = new C(); + c.ToString(); + } +}"; + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(source); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation") + .AddReferences(Mscorlib) + .AddSyntaxTrees(tree); + SemanticModel model = compilation.GetSemanticModel(tree); + + // Get position of 'c.ToString()' above. + int position = source.IndexOf("c.ToString()"); + + // Get IdentifierNameSyntax corresponding to identifier 'c' above. + IdentifierNameSyntax identifier = (IdentifierNameSyntax)tree.GetRoot().FindToken(position).Parent; + + // Get TypeSymbol corresponding to variable 'c' above. + ITypeSymbol type = model.GetTypeInfo(identifier).Type; + + // Get symbols for 'accessible' members on the above TypeSymbol. + System.Collections.Immutable.ImmutableArray symbols = model.LookupSymbols(position, container: type, + includeReducedExtensionMethods: true); + string results = string.Join("\r\n", symbols + .Select(symbol => symbol.ToDisplayString()) + .OrderBy(result => result)); + + Assert.Equal(@"C.ExtensionMethod() +C.InstanceField +C.InstanceMethod() +C.InstanceProperty +object.Equals(object) +object.Equals(object, object) +object.GetHashCode() +object.GetType() +object.ReferenceEquals(object, object) +object.ToString()", results); + } + + [FAQ(6)] + [Fact] + public void FindAllInvocationsOfAMethod() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +class C1 +{ + public void M1() { M2(); } + public void M2() { } +} +class C2 +{ + public void M1() { M2(); new C1().M2(); } + public void M2() { } +} +class Program +{ + static void Main() { } +}"); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation", + syntaxTrees: new[] { tree }, references: new[] { Mscorlib }); + SemanticModel model = compilation.GetSemanticModel(tree); + + // Get MethodDeclarationSyntax corresponding to method C1.M2() above. + MethodDeclarationSyntax methodDeclaration = tree.GetRoot() + .DescendantNodes().OfType().Single(c => c.Identifier.ValueText == "C1") + .DescendantNodes().OfType().Single(m => m.Identifier.ValueText == "M2"); + + // Get MethodSymbol corresponding to method C1.M2() above. + IMethodSymbol method = model.GetDeclaredSymbol(methodDeclaration); + + // Get all InvocationExpressionSyntax in the above code. + IEnumerable allInvocations = tree.GetRoot() + .DescendantNodes().OfType(); + + // Use GetSymbolInfo() to find invocations of method C1.M2() above. + IEnumerable matchingInvocations = allInvocations + .Where(i => model.GetSymbolInfo(i).Symbol.Equals(method)); + Assert.Equal(2, matchingInvocations.Count()); + } + + [FAQ(7)] + [Fact] + public void FindAllReferencesToAMethodInASolution() + { + string source1 = @" +namespace NS +{ + public class C + { + public void MethodThatWeAreTryingToFind() + { + } + public void AnotherMethod() + { + MethodThatWeAreTryingToFind(); // First Reference. + } + } +}"; + string source2 = @" +using NS; +using Alias=NS.C; +class Program +{ + static void Main() + { + var c1 = new C(); + c1.MethodThatWeAreTryingToFind(); // Second Reference. + c1.AnotherMethod(); + var c2 = new Alias(); + c2.MethodThatWeAreTryingToFind(); // Third Reference. + } +}"; + ProjectId project1Id = ProjectId.CreateNewId(); + ProjectId project2Id = ProjectId.CreateNewId(); + DocumentId document1Id = DocumentId.CreateNewId(project1Id); + DocumentId document2Id = DocumentId.CreateNewId(project2Id); + + Solution solution = new AdhocWorkspace().CurrentSolution + .AddProject(project1Id, "Project1", "Project1", LanguageNames.CSharp) + .AddMetadataReference(project1Id, Mscorlib) + .AddDocument(document1Id, "File1.cs", source1) + .WithProjectCompilationOptions(project1Id, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) + .AddProject(project2Id, "Project2", "Project2", LanguageNames.CSharp) + .AddMetadataReference(project2Id, Mscorlib) + .AddProjectReference(project2Id, new ProjectReference(project1Id)) + .AddDocument(document2Id, "File2.cs", source2); + + // If you wish to try against a real solution you could use code like + // var solution = Solution.Load(""); + // OR var solution = Workspace.LoadSolution("").CurrentSolution; + + Project project1 = solution.GetProject(project1Id); + Document document1 = project1.GetDocument(document1Id); + + // Get MethodDeclarationSyntax corresponding to the 'MethodThatWeAreTryingToFind'. + MethodDeclarationSyntax methodDeclaration = document1.GetSyntaxRootAsync().Result + .DescendantNodes().OfType() + .Single(m => m.Identifier.ValueText == "MethodThatWeAreTryingToFind"); + + // Get MethodSymbol corresponding to the 'MethodThatWeAreTryingToFind'. + IMethodSymbol method = (IMethodSymbol)document1.GetSemanticModelAsync().Result + .GetDeclaredSymbol(methodDeclaration); + + // Find all references to the 'MethodThatWeAreTryingToFind' in the solution. + IEnumerable methodReferences = SymbolFinder.FindReferencesAsync(method, solution).Result; + Assert.Single(methodReferences); + ReferencedSymbol methodReference = methodReferences.Single(); + Assert.Equal(3, methodReference.Locations.Count()); + + IMethodSymbol methodDefinition = (IMethodSymbol)methodReference.Definition; + Assert.Equal("MethodThatWeAreTryingToFind", methodDefinition.Name); + Assert.True(methodReference.Definition.Locations.Single().IsInSource); + Assert.Equal("File1.cs", methodReference.Definition.Locations.Single().SourceTree.FilePath); + + Assert.True(methodReference.Locations + .All(referenceLocation => referenceLocation.Location.IsInSource)); + Assert.Equal(1, methodReference.Locations + .Count(referenceLocation => referenceLocation.Document.Name == "File1.cs")); + Assert.Equal(2, methodReference.Locations + .Count(referenceLocation => referenceLocation.Document.Name == "File2.cs")); + } + + [FAQ(8)] + [Fact(Skip = "Need to load correct assembly references now that this is a .NET core app")] + public void FindAllInvocationsToMethodsFromAParticularNamespace() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +using System; +using System.Threading.Tasks; +class Program +{ + static void Main() + { + Action a = () => {}; + var t = Task.Factory.StartNew(a); + t.Wait(); + Console.WriteLine(a.ToString()); + + a = () => + { + t = new Task(a); + t.Start(); + t.Wait(); + }; + a(); + } +}"); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation") + .AddReferences(Mscorlib) + .AddSyntaxTrees(tree); + SemanticModel model = compilation.GetSemanticModel(tree); + + // Instantiate MethodInvocationWalker (below) and tell it to find invocations to methods from the System.Threading.Tasks namespace. + MethodInvocationWalker walker = new MethodInvocationWalker() + { + SemanticModel = model, + Namespace = "System.Threading.Tasks" + }; + + walker.Visit(tree.GetRoot()); + Assert.Equal(@" +Line 8: Task.Factory.StartNew(a) +Line 9: t.Wait() +Line 14: new Task(a) +Line 15: t.Start() +Line 16: t.Wait()", walker.Results.ToString()); + } + + // Below SyntaxWalker checks all nodes of type ObjectCreationExpressionSyntax or InvocationExpressionSyntax + // present under the SyntaxNode being visited to detect invocations to methods from the supplied namespace. + public class MethodInvocationWalker : CSharpSyntaxWalker + { + public SemanticModel SemanticModel { get; set; } + public string Namespace { get; set; } + public StringBuilder Results { get; private set; } + + public MethodInvocationWalker() + { + Results = new StringBuilder(); + } + + private bool CheckWhetherMethodIsFromNamespace(ExpressionSyntax node) + { + bool isMatch = false; + if (SemanticModel != null) + { + SymbolInfo symbolInfo = SemanticModel.GetSymbolInfo(node); + + string ns = symbolInfo.Symbol.ContainingNamespace.ToDisplayString(); + if (ns == Namespace) + { + Results.AppendLine(); + Results.Append("Line "); + Results.Append(SemanticModel.SyntaxTree.GetLineSpan(node.Span).StartLinePosition.Line); + Results.Append(": "); + Results.Append(node.ToString()); + isMatch = true; + } + } + + return isMatch; + } + + public override void VisitObjectCreationExpression(ObjectCreationExpressionSyntax node) + { + CheckWhetherMethodIsFromNamespace(node); + base.VisitObjectCreationExpression(node); + } + + public override void VisitInvocationExpression(InvocationExpressionSyntax node) + { + CheckWhetherMethodIsFromNamespace(node); + base.VisitInvocationExpression(node); + } + } + + [FAQ(9)] + [Fact] + public void GetAllFieldAndMethodSymbolsInACompilation() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +using System; +namespace NS1 +{ + public class C + { + int InstanceField = 0; + internal void InstanceMethod() + { + Console.WriteLine(InstanceField); + } + } +} +namespace NS2 +{ + static class ExtensionMethods + { + public static void ExtensionMethod(this NS1.C s) + { + } + } +} +class Program +{ + static void Main() + { + NS1.C c = new NS1.C(); + c.ToString(); + } +}"); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation", + syntaxTrees: new[] { tree }, references: new[] { Mscorlib }); + StringBuilder results = new StringBuilder(); + + // Traverse the symbol tree to find all namespaces, types, methods and fields. + foreach (INamespaceSymbol ns in compilation.Assembly.GlobalNamespace.GetNamespaceMembers()) + { + results.AppendLine(); + results.Append(ns.Kind); + results.Append(": "); + results.Append(ns.Name); + foreach (INamedTypeSymbol type in ns.GetTypeMembers()) + { + results.AppendLine(); + results.Append(" "); + results.Append(type.TypeKind); + results.Append(": "); + results.Append(type.Name); + foreach (ISymbol member in type.GetMembers()) + { + results.AppendLine(); + results.Append(" "); + if (member.Kind == SymbolKind.Field || member.Kind == SymbolKind.Method) + { + results.Append(member.Kind); + results.Append(": "); + results.Append(member.Name); + } + } + } + } + + Assert.Equal(@" +Namespace: NS1 + Class: C + Field: InstanceField + Method: InstanceMethod + Method: .ctor +Namespace: NS2 + Class: ExtensionMethods + Method: ExtensionMethod", results.ToString()); + } + + [FAQ(10)] + [Fact] + public void TraverseAllExpressionsInASyntaxTreeUsingAWalker() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +using System; +class Program +{ + static void Main() + { + var i = 0.0; + i += 1 + 2L; + } +}"); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation") + .AddReferences(Mscorlib) + .AddSyntaxTrees(tree); + SemanticModel model = compilation.GetSemanticModel(tree); + ExpressionWalker walker = new ExpressionWalker() { SemanticModel = model }; + + walker.Visit(tree.GetRoot()); + Assert.Equal(@" +PredefinedTypeSyntax void has type void +IdentifierNameSyntax var has type double +LiteralExpressionSyntax 0.0 has type double +AssignmentExpressionSyntax i += 1 + 2L has type double +IdentifierNameSyntax i has type double +BinaryExpressionSyntax 1 + 2L has type long +LiteralExpressionSyntax 1 has type int +LiteralExpressionSyntax 2L has type long", walker.Results.ToString()); + } + + // Below SyntaxWalker traverses all expressions under the SyntaxNode being visited and lists the types of these expressions. + public class ExpressionWalker : SyntaxWalker + { + public SemanticModel SemanticModel { get; set; } + public StringBuilder Results { get; private set; } + + public ExpressionWalker() + { + Results = new StringBuilder(); + } + + public override void Visit(SyntaxNode node) + { + if (node is ExpressionSyntax) + { + ITypeSymbol type = SemanticModel.GetTypeInfo((ExpressionSyntax)node).Type; + if (type != null) + { + Results.AppendLine(); + Results.Append(node.GetType().Name); + Results.Append(" "); + Results.Append(node.ToString()); + Results.Append(" has type "); + Results.Append(type.ToDisplayString()); + } + } + + base.Visit(node); + } + } + + [FAQ(11)] + [Fact] + public void CompareSyntax() + { + string source = @" +using System; +class Program +{ + static void Main() + { + var i = 0.0; + i += 1 + 2L; + } +}"; + SyntaxTree tree1 = SyntaxFactory.ParseSyntaxTree(source); + SyntaxTree tree2 = SyntaxFactory.ParseSyntaxTree(source); + SyntaxNode node1 = tree1.GetRoot(); + SyntaxNode node2 = tree2.GetRoot(); + + // Compare trees and nodes that are identical. + Assert.True(tree1.IsEquivalentTo(tree2)); + Assert.True(node1.IsEquivalentTo(node2)); + Assert.True(SyntaxFactory.AreEquivalent(node1, node2, topLevel: false)); + Assert.True(SyntaxFactory.AreEquivalent(tree1, tree2, topLevel: false)); + + // tree3 is identical to tree1 except for a single comment. + SyntaxTree tree3 = SyntaxFactory.ParseSyntaxTree(@" +using System; +class Program +{ + // Additional comment. + static void Main() + { + var i = 0.0; + i += 1 + 2L; + } +}"); + SyntaxNode node3 = tree3.GetRoot(); + + // Compare trees and nodes that are identical except for trivia. + Assert.True(tree1.IsEquivalentTo(tree3)); // Trivia differences are ignored. + Assert.False(node1.IsEquivalentTo(node3)); // Trivia differences are considered. + Assert.True(SyntaxFactory.AreEquivalent(node1, node3, topLevel: false)); // Trivia differences are ignored. + Assert.True(SyntaxFactory.AreEquivalent(tree1, tree3, topLevel: false)); // Trivia differences are ignored. + + // tree4 is identical to tree1 except for method body contents. + SyntaxTree tree4 = SyntaxFactory.ParseSyntaxTree(@"using System; +class Program +{ + static void Main() + { + } +}"); + SyntaxNode node4 = tree4.GetRoot(); + + // Compare trees and nodes that are identical at the top-level. + Assert.True(tree1.IsEquivalentTo(tree4, topLevel: true)); // Only top-level nodes are considered. + Assert.False(node1.IsEquivalentTo(node4)); // Non-top-level nodes are considered. + Assert.True(SyntaxFactory.AreEquivalent(node1, node4, topLevel: true)); // Only top-level nodes are considered. + Assert.True(SyntaxFactory.AreEquivalent(tree1, tree4, topLevel: true)); // Only top-level nodes are considered. + + // Tokens and Trivia can also be compared. + SyntaxToken token1 = node1.DescendantTokens().First(); + SyntaxToken token2 = node2.DescendantTokens().First(); + Assert.True(token1.IsEquivalentTo(token2)); + SyntaxTrivia trivia1 = node1.DescendantTrivia().First(t => t.Kind() == SyntaxKind.WhitespaceTrivia); + SyntaxTrivia trivia2 = node2.DescendantTrivia().Last(t => t.Kind() == SyntaxKind.EndOfLineTrivia); + Assert.False(trivia1.IsEquivalentTo(trivia2)); + } + + [FAQ(29)] + [Fact] + public void TraverseAllCommentsInASyntaxTreeUsingAWalker() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +using System; +/// First Comment +class Program +{ + /* Second Comment */ + static void Main() + { + // Third Comment + } +}"); + CommentWalker walker = new CommentWalker(); + walker.Visit(tree.GetRoot()); + + Assert.Equal(@" +/// First Comment (Parent Token: ClassKeyword) (Structured) +/* Second Comment */ (Parent Token: StaticKeyword) +// Third Comment (Parent Token: CloseBraceToken)", walker.Results.ToString()); + } + + // Below SyntaxWalker traverses all comments present under the SyntaxNode being visited. + public class CommentWalker : CSharpSyntaxWalker + { + public StringBuilder Results { get; private set; } + + public CommentWalker() : + base(SyntaxWalkerDepth.StructuredTrivia) + { + Results = new StringBuilder(); + } + + public override void VisitTrivia(SyntaxTrivia trivia) + { + bool isDocComment = SyntaxFacts.IsDocumentationCommentTrivia(trivia.Kind()); + if (isDocComment || + trivia.Kind() == SyntaxKind.SingleLineCommentTrivia || + trivia.Kind() == SyntaxKind.MultiLineCommentTrivia) + { + Results.AppendLine(); + Results.Append(trivia.ToFullString().Trim()); + Results.Append(" (Parent Token: "); + Results.Append(trivia.Token.Kind()); + Results.Append(")"); + if (isDocComment) + { + // Trivia for xml documentation comments have additional 'structure' + // available under a child DocumentationCommentSyntax. + Assert.True(trivia.HasStructure); + DocumentationCommentTriviaSyntax documentationComment = + (DocumentationCommentTriviaSyntax)trivia.GetStructure(); + Assert.True(documentationComment.ParentTrivia == trivia); + Results.Append(" (Structured)"); + } + } + + base.VisitTrivia(trivia); + } + } + + [FAQ(12)] + [Fact] + public void CompareSymbols() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +using System; +class C +{ +} +class Program +{ + public static void Main() + { + var c = new C(); Console.WriteLine(c.ToString()); + } +}"); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation", + syntaxTrees: new[] { tree }, references: new[] { Mscorlib }); + SemanticModel model = compilation.GetSemanticModel(tree); + + // Get VariableDeclaratorSyntax corresponding to the statement 'var c = ...' above. + VariableDeclaratorSyntax variableDeclarator = tree.GetRoot() + .DescendantNodes().OfType().Single(); + + // Get TypeSymbol corresponding to 'var c' above. + ITypeSymbol type = ((ILocalSymbol)model.GetDeclaredSymbol(variableDeclarator)).Type; + + ITypeSymbol expectedType = compilation.GetTypeByMetadataName("C"); + Assert.True(type.Equals(expectedType)); + } + + [FAQ(13)] + [Fact] + public void TestWhetherANodeIsPartOfATreeOrASemanticModel() + { + string source = @" +using System; +class C +{ +} +class Program +{ + public static void Main() + { + var c = new C(); Console.WriteLine(c.ToString()); + } +}"; + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(source); + SyntaxTree other = SyntaxFactory.ParseSyntaxTree(source); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation") + .AddReferences(Mscorlib) + .AddSyntaxTrees(tree); + SemanticModel model = compilation.GetSemanticModel(tree); + + SyntaxNode nodeFromTree = tree.GetRoot(); + SyntaxToken tokenNotFromTree = SyntaxFactory.Token(SyntaxKind.ClassKeyword); + SyntaxNode nodeNotFromTree = other.GetRoot(); + + Assert.Equal(nodeFromTree.SyntaxTree, tree); + Assert.Equal(nodeFromTree.SyntaxTree, model.SyntaxTree); + Assert.NotEqual(tokenNotFromTree.SyntaxTree, tree); + Assert.NotEqual(nodeNotFromTree.SyntaxTree, model.SyntaxTree); + Assert.Equal(nodeNotFromTree.SyntaxTree, other); + } + + [FAQ(14)] + [Fact] + public void ValueVersusValueTextVersusGetTextForTokens() + { + string source = @" +using System; +class Program +{ + public static void Main() + { + var @long = 1L; Console.WriteLine(@long); + } +}"; + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(source); + + // Get token corresponding to identifier '@long' above. + SyntaxToken token1 = tree.GetRoot().FindToken(source.IndexOf("@long")); + + // Get token corresponding to literal '1L' above. + SyntaxToken token2 = tree.GetRoot().FindToken(source.IndexOf("1L")); + + Assert.Equal("String", token1.Value.GetType().Name); + Assert.Equal("long", token1.Value); + Assert.Equal("long", token1.ValueText); + Assert.Equal("@long", token1.ToString()); + + Assert.Equal("Int64", token2.Value.GetType().Name); + Assert.Equal(1L, token2.Value); + Assert.Equal("1", token2.ValueText); + Assert.Equal("1L", token2.ToString()); + } + + [FAQ(16)] + [Fact] + public void GetLineAndColumnInfo() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +class Program +{ + public static void Main() + { + } +}", path: "MyCodeFile.cs"); + + // Get BlockSyntax corresponding to the method block for 'void Main()' above. + BlockSyntax node = (BlockSyntax)tree.GetRoot().DescendantNodes().Last(); + + // Use GetLocation() and GetLineSpan() to get file, line and column info for above BlockSyntax. + Location location = node.GetLocation(); + FileLinePositionSpan lineSpan = location.GetLineSpan(); + Assert.True(location.IsInSource); + Assert.Equal("MyCodeFile.cs", lineSpan.Path); + Assert.Equal(4, lineSpan.StartLinePosition.Line); + Assert.Equal(4, lineSpan.StartLinePosition.Character); + + // Alternate way to get file, line and column info from any span. + location = tree.GetLocation(node.Span); + lineSpan = location.GetLineSpan(); + Assert.Equal("MyCodeFile.cs", lineSpan.Path); + Assert.Equal(4, lineSpan.StartLinePosition.Line); + Assert.Equal(4, lineSpan.StartLinePosition.Character); + + // Yet another way to get file, line and column info from any span. + lineSpan = tree.GetLineSpan(node.Span); + Assert.Equal("MyCodeFile.cs", lineSpan.Path); + Assert.Equal(5, lineSpan.EndLinePosition.Line); + Assert.Equal(5, lineSpan.EndLinePosition.Character); + + // SyntaxTokens also have GetLocation(). + // Use GetLocation() to get the position of the '{' token under the above BlockSyntax. + SyntaxToken token = node.DescendantTokens().First(); + location = token.GetLocation(); + lineSpan = location.GetLineSpan(); + Assert.Equal("MyCodeFile.cs", lineSpan.Path); + Assert.Equal(4, lineSpan.StartLinePosition.Line); + Assert.Equal(4, lineSpan.StartLinePosition.Character); + + // SyntaxTrivia also have GetLocation(). + // Use GetLocation() to get the position of the first WhiteSpaceTrivia under the above SyntaxToken. + SyntaxTrivia trivia = token.LeadingTrivia.First(); + location = trivia.GetLocation(); + lineSpan = location.GetLineSpan(); + Assert.Equal("MyCodeFile.cs", lineSpan.Path); + Assert.Equal(4, lineSpan.StartLinePosition.Line); + Assert.Equal(0, lineSpan.StartLinePosition.Character); + } + + [FAQ(17)] + [Fact] + public void GetEmptySourceLinesFromASyntaxTree() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +class Program +{ + public static void Main() + { + + } +}", path: "MyCodeFile.cs"); + SourceText text = tree.GetText(); + Assert.Equal(8, text.Lines.Count); + + // Enumerate empty lines. + string results = string.Join("\r\n", text.Lines + .Where(line => string.IsNullOrWhiteSpace(line.ToString())) + .Select(line => string.Format("Line {0} (Span {1}-{2}) is empty", line.LineNumber, line.Start, line.End))); + + Assert.Equal(@"Line 0 (Span 0-0) is empty +Line 5 (Span 58-66) is empty", results); + } + + [FAQ(18)] + [Fact] + public void UseSyntaxWalker() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +class Program +{ + public static void Main() + { +#if true +#endif + var b = true; + if (b) { } + if (!b) { } + } +} +struct S +{ +}"); + IfStatementIfKeywordAndTypeDeclarationWalker walker = new IfStatementIfKeywordAndTypeDeclarationWalker(); + walker.Visit(tree.GetRoot()); + + Assert.Equal(@" +Visiting ClassDeclarationSyntax (Kind = ClassDeclaration) +Visiting SyntaxToken (Kind = IfKeyword): #if true +Visiting IfStatementSyntax (Kind = IfStatement): if (b) { } +Visiting SyntaxToken (Kind = IfKeyword): if (b) { } +Visiting IfStatementSyntax (Kind = IfStatement): if (!b) { } +Visiting SyntaxToken (Kind = IfKeyword): if (!b) { } +Visiting StructDeclarationSyntax (Kind = StructDeclaration)", walker.Results.ToString()); + } + + // Below SyntaxWalker traverses all IfStatementSyntax, IfKeyworkd and TypeDeclarationSyntax present under the SyntaxNode being visited. + public class IfStatementIfKeywordAndTypeDeclarationWalker : CSharpSyntaxWalker + { + public StringBuilder Results { get; private set; } + + // Turn on visiting of nodes, tokens and trivia present under structured trivia. + public IfStatementIfKeywordAndTypeDeclarationWalker() + : base(SyntaxWalkerDepth.StructuredTrivia) + { + Results = new StringBuilder(); + } + + // If you need to visit all SyntaxNodes of a particular (derived) type that appears directly + // in a syntax tree, you can override the Visit* method corresponding to this type. + // For example, you can override VisitIfStatement to visit all SyntaxNodes of type IfStatementSyntax. + public override void VisitIfStatement(IfStatementSyntax node) + { + Results.AppendLine(); + Results.Append("Visiting "); + Results.Append(node.GetType().Name); + Results.Append(" (Kind = "); + Results.Append(node.Kind().ToString()); + Results.Append("): "); + Results.Append(node.ToString()); + base.VisitIfStatement(node); + } + + // Visits all SyntaxTokens. + public override void VisitToken(SyntaxToken token) + { + // We only care about SyntaxTokens with Kind 'IfKeyword'. + if (token.Kind() == SyntaxKind.IfKeyword) + { + Results.AppendLine(); + Results.Append("Visiting "); + Results.Append(token.GetType().Name); + Results.Append(" (Kind = "); + Results.Append(token.Kind().ToString()); + Results.Append("): "); + Results.Append(token.Parent.ToString()); + } + + base.VisitToken(token); + } + + // Visits all SyntaxNodes. + public override void Visit(SyntaxNode node) + { + // If you need to visit all SyntaxNodes of a particular base type that can never + // appear directly in a syntax tree then this would be the place to check for that. + // For example, TypeDeclarationSyntax is a base type for all the type declarations (like + // ClassDeclarationSyntax and StructDeclarationSyntax) that can appear in a syntax tree. + if (node is TypeDeclarationSyntax) + { + Results.AppendLine(); + Results.Append("Visiting "); + Results.Append(node.GetType().Name); + Results.Append(" (Kind = "); + Results.Append(node.Kind().ToString()); + Results.Append(")"); + } + + base.Visit(node); + } + } + + [FAQ(19)] + [Fact] + public void GetFullyQualifiedName() + { + string source = @" +using System; +using Alias=NS.C; +namespace NS +{ + public class C + { + public struct S + { + } + } +} +class Program +{ + public static void Main() + { + Alias.S s = new Alias.S(); Console.WriteLine(s.ToString()); + } +}"; + ProjectId projectId = ProjectId.CreateNewId(); + DocumentId documentId = DocumentId.CreateNewId(projectId); + + Solution solution = new AdhocWorkspace().CurrentSolution + .AddProject(projectId, "MyProject", "MyProject", LanguageNames.CSharp) + .AddMetadataReference(projectId, Mscorlib) + .AddDocument(documentId, "MyFile.cs", source); + Document document = solution.GetDocument(documentId); + SyntaxNode root = document.GetSyntaxRootAsync().Result; + SemanticModel model = (SemanticModel)document.GetSemanticModelAsync().Result; + + // Get StructDeclarationSyntax corresponding to 'struct S' above. + StructDeclarationSyntax structDeclaration = root.DescendantNodes() + .OfType().Single(); + + // Get TypeSymbol corresponding to 'struct S' above. + INamedTypeSymbol structType = model.GetDeclaredSymbol(structDeclaration); + + // Use ToDisplayString() to get fully qualified name. + Assert.Equal("NS.C.S", structType.ToDisplayString()); + Assert.Equal("global::NS.C.S", structType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + + // Get VariableDeclaratorSyntax corresponding to 'Alias.S s = ...' above. + VariableDeclaratorSyntax variableDeclarator = root.DescendantNodes() + .OfType().Single(); + + // Get TypeSymbol corresponding to above VariableDeclaratorSyntax. + ITypeSymbol variableType = ((ILocalSymbol)model.GetDeclaredSymbol(variableDeclarator)).Type; + + Assert.False(variableType.Equals(structType)); // Type of variable is a closed generic type while that of the struct is an open generic type. + Assert.True(variableType.OriginalDefinition.Equals(structType)); // OriginalDefinition for a closed generic type points to corresponding open generic type. + Assert.Equal("NS.C.S", variableType.ToDisplayString()); + Assert.Equal("global::NS.C.S", variableType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + } + + [FAQ(20)] + [Fact] + public void OverloadBindingDetermination() + { + string source = @" +using System; +class Program +{ + private int Identity(int a) + { + return a; + } + + private char Identity(char a) + { + return a; + } + + static void Main() + { + var v1 = Identity(3); + var v2 = Identity('a'); + var v3 = Identity(""arg1"") + }; +} +"; + + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(source); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation", new[] { tree }, new[] { Mscorlib }); + SemanticModel model = compilation.GetSemanticModel(tree); + + IEnumerable allInvocations = tree.GetRoot().DescendantNodes().OfType(); + + // Below, we expect to find that the Method taking an int was selected. + // We can confidently index into the invocations because we are following the source line-by-line. This is not always a safe practice. + InvocationExpressionSyntax intInvocation = allInvocations.ElementAt(0); + SymbolInfo info = model.GetSymbolInfo(intInvocation); + Assert.NotNull(info.Symbol); + Assert.Equal("Program.Identity(int)", info.Symbol.ToDisplayString()); + + // Below, we expect to find that the Method taking a char was selected. + InvocationExpressionSyntax charInvocation = allInvocations.ElementAt(1); + info = model.GetSymbolInfo(charInvocation); + Assert.NotNull(info.Symbol); + Assert.Equal("Program.Identity(char)", info.Symbol.ToDisplayString()); + + // Below, we expect to find that no suitable Method was found, and therefore none were selected. + InvocationExpressionSyntax stringInvocation = allInvocations.ElementAt(2); + info = model.GetSymbolInfo(stringInvocation); + Assert.Null(info.Symbol); + Assert.Equal(2, info.CandidateSymbols.Length); + Assert.Equal(CandidateReason.OverloadResolutionFailure, info.CandidateReason); + } + + [FAQ(21)] + [Fact] + public void ClassifyConversionFromAnExpressionToATypeSymbol() + { + string source = @" +using System; +class Program +{ + static void M() + { + } + static void M(long l) + { + } + static void M(short s) + { + } + static void M(int i) + { + } + static void Main() + { + int ii = 0; + Console.WriteLine(ii); + short jj = 1; + Console.WriteLine(jj); + string ss = string.Empty; + Console.WriteLine(ss); + + // Perform conversion classification here. + } +}"; + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(source); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation") + .AddReferences(Mscorlib) + .AddSyntaxTrees(tree); + SemanticModel model = compilation.GetSemanticModel(tree); + + // Get VariableDeclaratorSyntax corresponding to variable 'ii' above. + VariableDeclaratorSyntax variableDeclarator = (VariableDeclaratorSyntax)tree.GetRoot() + .FindToken(source.IndexOf("ii")).Parent; + + // Get TypeSymbol corresponding to above VariableDeclaratorSyntax. + ITypeSymbol targetType = ((ILocalSymbol)model.GetDeclaredSymbol(variableDeclarator)).Type; + + // Perform ClassifyConversion for expressions from within the above SyntaxTree. + ExpressionSyntax sourceExpression1 = (ExpressionSyntax)tree.GetRoot() + .FindToken(source.IndexOf("jj)")).Parent; + Conversion conversion = model.ClassifyConversion(sourceExpression1, targetType); + Assert.True(conversion.IsImplicit && conversion.IsNumeric); + + ExpressionSyntax sourceExpression2 = (ExpressionSyntax)tree.GetRoot() + .FindToken(source.IndexOf("ss)")).Parent; + conversion = model.ClassifyConversion(sourceExpression2, targetType); + Assert.False(conversion.Exists); + + // Perform ClassifyConversion for constructed expressions + // at the position identified by the comment '// Perform ...' above. + ExpressionSyntax sourceExpression3 = SyntaxFactory.IdentifierName("jj"); + int position = source.IndexOf("//"); + conversion = model.ClassifyConversion(position, sourceExpression3, targetType); + Assert.True(conversion.IsImplicit && conversion.IsNumeric); + + ExpressionSyntax sourceExpression4 = SyntaxFactory.IdentifierName("ss"); + conversion = model.ClassifyConversion(position, sourceExpression4, targetType); + Assert.False(conversion.Exists); + + ExpressionSyntax sourceExpression5 = SyntaxFactory.ParseExpression("100L"); + conversion = model.ClassifyConversion(position, sourceExpression5, targetType); + Assert.True(conversion.IsExplicit && conversion.IsNumeric); + } + + [FAQ(22)] + [Fact] + public void ClassifyConversionFromOneTypeSymbolToAnother() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +class Program +{ + static void Main() { } +}"); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation", + syntaxTrees: new[] { tree }, references: new[] { Mscorlib }); + + INamedTypeSymbol int32Type = compilation.GetSpecialType(SpecialType.System_Int32); + INamedTypeSymbol int16Type = compilation.GetSpecialType(SpecialType.System_Int16); + INamedTypeSymbol stringType = compilation.GetSpecialType(SpecialType.System_String); + INamedTypeSymbol int64Type = compilation.GetSpecialType(SpecialType.System_Int64); + + Assert.True(compilation.ClassifyConversion(int32Type, int32Type).IsIdentity); + + Conversion conversion1 = compilation.ClassifyConversion(int16Type, int32Type); + + Assert.True(conversion1.IsImplicit && conversion1.IsNumeric); + + Assert.False(compilation.ClassifyConversion(stringType, int32Type).Exists); + + Conversion conversion2 = compilation.ClassifyConversion(int64Type, int32Type); + + Assert.True(conversion2.IsExplicit && conversion2.IsNumeric); + } + + [FAQ(23)] + [Fact] + public void GetTargetFrameworkVersionForCompilation() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +class Program +{ + static void Main() { } +}"); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation") + .AddReferences(Mscorlib) + .AddSyntaxTrees(tree); + + Version version = compilation.GetSpecialType(SpecialType.System_Object).ContainingAssembly.Identity.Version; + Assert.Equal(4, version.Major); + } + + [FAQ(24)] + [Fact] + public void GetAssemblySymbolsAndSyntaxTreesFromAProject() + { + string source = @" +class Program +{ + static void Main() + { + } +}"; + ProjectId projectId = ProjectId.CreateNewId(); + DocumentId documentId = DocumentId.CreateNewId(projectId); + + Solution solution = new AdhocWorkspace().CurrentSolution + .AddProject(projectId, "MyProject", "MyProject", LanguageNames.CSharp) + .AddMetadataReference(projectId, Mscorlib) + .AddDocument(documentId, "MyFile.cs", source); + + // If you wish to try against a real project you could use code like + // var project = Solution.LoadStandaloneProject(""); + // OR var project = Workspace.LoadStandaloneProject("").CurrentSolution.Projects.First(); + + Project project = solution.Projects.Single(); + Compilation compilation = project.GetCompilationAsync().Result; + + // Get AssemblySymbols for above compilation and the assembly (mscorlib) referenced by it. + IAssemblySymbol compilationAssembly = compilation.Assembly; + IAssemblySymbol referencedAssembly = (IAssemblySymbol)compilation.GetAssemblyOrModuleSymbol(project.MetadataReferences.Single()); + + Assert.True(compilation.GetTypeByMetadataName("Program").ContainingAssembly.Equals(compilationAssembly)); + Assert.True(compilation.GetTypeByMetadataName("System.Object").ContainingAssembly.Equals(referencedAssembly)); + + SyntaxTree tree = project.Documents.Single().GetSyntaxTreeAsync().Result; + Assert.Equal("MyFile.cs", tree.FilePath); + } + + [FAQ(25)] + [Fact] + public void UseSyntaxAnnotations() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +using System; +class Program +{ + static void Main() + { + int i = 0; Console.WriteLine(i); + } +}"); + + // Tag all tokens that contain the letter 'i'. + MyAnnotator rewriter = new MyAnnotator(); + SyntaxNode oldRoot = tree.GetRoot(); + SyntaxNode newRoot = rewriter.Visit(oldRoot); + + Assert.False(oldRoot.ContainsAnnotations); + Assert.True(newRoot.ContainsAnnotations); + + // Find all tokens that were tagged with annotations of type MyAnnotation. + IEnumerable annotatedTokens = newRoot.GetAnnotatedNodesAndTokens(MyAnnotation.Kind); + string results = string.Join("\r\n", + annotatedTokens.Select(nodeOrToken => + { + Assert.True(nodeOrToken.IsToken); + SyntaxAnnotation annotation = nodeOrToken.GetAnnotations(MyAnnotation.Kind).Single(); + return string.Format("{0} (position {1})", nodeOrToken.ToString(), MyAnnotation.GetPosition(annotation)); + })); + + Assert.Equal(@"using (position 2) +static (position 4) +void (position 2) +Main (position 2) +int (position 0) +i (position 0) +WriteLine (position 2) +i (position 0)", results); + } + + // Below CSharpSyntaxRewriter tags all SyntaxTokens that contain the lowercase letter 'i' under the SyntaxNode being visited. + public class MyAnnotator : CSharpSyntaxRewriter + { + public override SyntaxToken VisitToken(SyntaxToken token) + { + SyntaxToken newToken = base.VisitToken(token); + int position = token.ToString().IndexOf('i'); + if (position >= 0) + { + newToken = newToken.WithAdditionalAnnotations(MyAnnotation.Create(position)); + } + + return newToken; + } + } + + public static class MyAnnotation + { + public const string Kind = "MyAnnotation"; + + public static SyntaxAnnotation Create(int position) + { + return new SyntaxAnnotation(Kind, position.ToString()); + } + + public static int GetPosition(SyntaxAnnotation annotation) + { + return int.Parse(annotation.Data); + } + } + + [FAQ(37)] + [Fact] + public void GetBaseTypesAndOverridingRelationships() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +using System; +abstract class C1 +{ + public virtual int F1(short s) { return 0; } + public abstract int P1 { get; set; } +} +abstract class C2 : C1 +{ + public new virtual int F1(short s) { return 1; } +} +class C3 : C2 +{ + public override sealed int F1(short s) { return 2; } + public override int P1 { get; set; } +}"); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation", + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary), + syntaxTrees: new[] { tree }, references: new[] { Mscorlib }); + + // Get TypeSymbols for types C1, C2 and C3 above. + INamedTypeSymbol typeC1 = compilation.GetTypeByMetadataName("C1"); + INamedTypeSymbol typeC2 = compilation.GetTypeByMetadataName("C2"); + INamedTypeSymbol typeC3 = compilation.GetTypeByMetadataName("C3"); + INamedTypeSymbol typeObject = compilation.GetSpecialType(SpecialType.System_Object); + + Assert.True(typeC1.IsAbstract); + Assert.True(typeC2.IsAbstract); + Assert.False(typeC3.IsAbstract); + + // Get TypeSymbols for base types of C1, C2 and C3 above. + Assert.True(typeC1.BaseType.Equals(typeObject)); + Assert.True(typeC2.BaseType.Equals(typeC1)); + Assert.True(typeC3.BaseType.Equals(typeC2)); + + // Get MethodSymbols for methods named F1 in types C1, C2 and C3 above. + IMethodSymbol methodC1F1 = (IMethodSymbol)typeC1.GetMembers("F1").Single(); + IMethodSymbol methodC2F1 = (IMethodSymbol)typeC2.GetMembers("F1").Single(); + IMethodSymbol methodC3F1 = (IMethodSymbol)typeC3.GetMembers("F1").Single(); + + // Get overriding relationships between above MethodSymbols. + Assert.True(methodC1F1.IsVirtual); + Assert.True(methodC2F1.IsVirtual); + Assert.False(methodC2F1.IsOverride); + Assert.True(methodC3F1.IsOverride); + Assert.True(methodC3F1.IsSealed); + Assert.True(methodC3F1.OverriddenMethod.Equals(methodC2F1)); + Assert.False(methodC3F1.OverriddenMethod.Equals(methodC1F1)); + + // Get PropertySymbols for properties named P1 in types C1 and C3 above. + IPropertySymbol propertyC1P1 = (IPropertySymbol)typeC1.GetMembers("P1").Single(); + IPropertySymbol propertyC3P1 = (IPropertySymbol)typeC3.GetMembers("P1").Single(); + + // Get overriding relationships between above PropertySymbols. + Assert.True(propertyC1P1.IsAbstract); + Assert.False(propertyC1P1.IsVirtual); + Assert.True(propertyC3P1.IsOverride); + Assert.True(propertyC3P1.OverriddenProperty.Equals(propertyC1P1)); + } + + [FAQ(38)] + [Fact] + public void GetInterfacesAndImplementationRelationships() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +using System; +interface I1 +{ + void M1(); + int P1 { get; set; } +} +interface I2 : I1 +{ + void M2(); +} +class C1 : I1 +{ + public void M1() { } + public virtual int P1 { get; set; } + public void M2() { } +} +class C2 : C1, I2 +{ + new public void M1() { } +} +class C3 : C2, I1 +{ + public override int P1 { get; set; } + int I1.P1 { get; set; } +}"); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation", + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary), + syntaxTrees: new[] { tree }, references: new[] { Mscorlib }); + + // Get TypeSymbols for types I1, I2, C1, C2 and C3 above. + INamedTypeSymbol typeI1 = compilation.GetTypeByMetadataName("I1"); + INamedTypeSymbol typeI2 = compilation.GetTypeByMetadataName("I2"); + INamedTypeSymbol typeC1 = compilation.GetTypeByMetadataName("C1"); + INamedTypeSymbol typeC2 = compilation.GetTypeByMetadataName("C2"); + INamedTypeSymbol typeC3 = compilation.GetTypeByMetadataName("C3"); + + Assert.Null(typeI1.BaseType); + Assert.Null(typeI2.BaseType); + Assert.Empty(typeI1.Interfaces); + Assert.True(typeI2.Interfaces.Single().Equals(typeI1)); + + // Get TypeSymbol for interface implemented by C1 above. + Assert.True(typeC1.Interfaces.Single().Equals(typeI1)); + + // Get TypeSymbols for interfaces implemented by C2 above. + Assert.True(typeC2.Interfaces.Single().Equals(typeI2)); + Assert.Equal(2, typeC2.AllInterfaces.Length); + Assert.NotNull(typeC2.AllInterfaces.Single(type => type.Equals(typeI1))); + Assert.NotNull(typeC2.AllInterfaces.Single(type => type.Equals(typeI2))); + + // Get TypeSymbols for interfaces implemented by C3 above. + Assert.True(typeC3.Interfaces.Single().Equals(typeI1)); + Assert.Equal(2, typeC3.AllInterfaces.Length); + Assert.NotNull(typeC3.AllInterfaces.Single(type => type.Equals(typeI1))); + Assert.NotNull(typeC3.AllInterfaces.Single(type => type.Equals(typeI2))); + + // Get MethodSymbols for methods named M1 and M2 in types I1, I2, C1 and C2 above. + IMethodSymbol methodI1M1 = (IMethodSymbol)typeI1.GetMembers("M1").Single(); + IMethodSymbol methodI2M2 = (IMethodSymbol)typeI2.GetMembers("M2").Single(); + IMethodSymbol methodC1M1 = (IMethodSymbol)typeC1.GetMembers("M1").Single(); + IMethodSymbol methodC1M2 = (IMethodSymbol)typeC1.GetMembers("M2").Single(); + IMethodSymbol methodC2M1 = (IMethodSymbol)typeC2.GetMembers("M1").Single(); + + // Get interface implementation relationships between above MethodSymbols. + Assert.True(typeC1.FindImplementationForInterfaceMember(methodI1M1).Equals(methodC1M1)); + Assert.True(typeC2.FindImplementationForInterfaceMember(methodI1M1).Equals(methodC2M1)); + Assert.True(typeC2.FindImplementationForInterfaceMember(methodI2M2).Equals(methodC1M2)); + Assert.True(typeC3.FindImplementationForInterfaceMember(methodI1M1).Equals(methodC2M1)); + Assert.True(typeC3.FindImplementationForInterfaceMember(methodI2M2).Equals(methodC1M2)); + + // Get PropertySymbols for properties named P1 in types I1, C1 and C3 above. + IPropertySymbol propertyI1P1 = (IPropertySymbol)typeI1.GetMembers("P1").Single(); + IPropertySymbol propertyC1P1 = (IPropertySymbol)typeC1.GetMembers("P1").Single(); + IPropertySymbol propertyC3P1 = (IPropertySymbol)typeC3.GetMembers("P1").Single(); + IPropertySymbol propertyC3I1P1 = (IPropertySymbol)typeC3.GetMembers("I1.P1").Single(); + + // Get interface implementation relationships between above PropertySymbols. + Assert.True(typeC1.FindImplementationForInterfaceMember(propertyI1P1).Equals(propertyC1P1)); + Assert.True(typeC2.FindImplementationForInterfaceMember(propertyI1P1).Equals(propertyC1P1)); + Assert.True(typeC3.FindImplementationForInterfaceMember(propertyI1P1).Equals(propertyC3I1P1)); + Assert.False(typeC3.FindImplementationForInterfaceMember(propertyI1P1).Equals(propertyC3P1)); + + Assert.True(propertyC3I1P1.ExplicitInterfaceImplementations.Single().Equals(propertyI1P1)); + } + + [FAQ(39)] + [Fact] + public void GetAppliedAttributes() + { + string source = @" +using System; + +class Class1 +{ + [AttributeUsage(AttributeTargets.Method)] + private class ExampleAttribute : Attribute + { + private readonly int _id; + + public int Id + { + get + { + return _id; + } + } + + public ExampleAttribute(int id) + { + _id = id; + } + } + + static void Method1() + { + // Intentionally left blank + } + + [ExampleAttribute(1)] + static void Method2() + { + // Intentionally left blank + } + + [ExampleAttribute(2)] + static void Method3() + { + // Intentionally left blank + } +} +"; + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(source); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation", + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary), + syntaxTrees: new[] { tree }, + references: new[] { Mscorlib }); + System.Collections.Immutable.ImmutableArray diagnostics = compilation.GetDiagnostics(); + Assert.Empty(diagnostics); + SemanticModel model = compilation.GetSemanticModel(tree); + + IMethodSymbol getMethod(string name) => (from declaration in tree.GetRoot().DescendantNodes().OfType() + where declaration.Identifier.Text.Equals(name) + select model.GetDeclaredSymbol(declaration)).Single(); + INamedTypeSymbol attributeSymbol = (from declaration in tree.GetRoot().DescendantNodes().OfType() + where declaration.Identifier.Text.Equals("ExampleAttribute") + select model.GetDeclaredSymbol(declaration)).Single(); + + // Verify that a method has no attributes + IMethodSymbol methodSymbol = getMethod("Method1"); + Assert.Empty(methodSymbol.GetAttributes()); + + // Inspect the attributes that have been given to methods 2 and 3 + methodSymbol = getMethod("Method2"); + AttributeData appliedAttribute = methodSymbol.GetAttributes().Single(); + Assert.Equal(attributeSymbol, appliedAttribute.AttributeClass); + Assert.Equal(TypedConstantKind.Primitive, appliedAttribute.ConstructorArguments[0].Kind); + Assert.Equal(1, (int)appliedAttribute.ConstructorArguments[0].Value); + + methodSymbol = getMethod("Method3"); + appliedAttribute = methodSymbol.GetAttributes().Single(); + Assert.Equal(attributeSymbol, appliedAttribute.AttributeClass); + Assert.Equal(TypedConstantKind.Primitive, appliedAttribute.ConstructorArguments[0].Kind); + Assert.Equal(2, (int)appliedAttribute.ConstructorArguments[0].Value); + } + #endregion + + #region Section 2 : Constructing & Updating Tree Questions + [FAQ(26)] + [Fact] + public void AddMethodToClass() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +class C +{ +}"); + CompilationUnitSyntax compilationUnit = (CompilationUnitSyntax)tree.GetRoot(); + + // Get ClassDeclarationSyntax corresponding to 'class C' above. + ClassDeclarationSyntax classDeclaration = compilationUnit.ChildNodes() + .OfType().Single(); + + // Construct a new MethodDeclarationSyntax. + MethodDeclarationSyntax newMethodDeclaration = + SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName("void"), "M") + .WithBody(SyntaxFactory.Block()); + + // Add this new MethodDeclarationSyntax to the above ClassDeclarationSyntax. + ClassDeclarationSyntax newClassDeclaration = + classDeclaration.AddMembers(newMethodDeclaration); + + // Update the CompilationUnitSyntax with the new ClassDeclarationSyntax. + CompilationUnitSyntax newCompilationUnit = + compilationUnit.ReplaceNode(classDeclaration, newClassDeclaration); + + // normalize the whitespace + newCompilationUnit = newCompilationUnit.NormalizeWhitespace(" "); + + Assert.Equal( +@"class C +{ + void M() + { + } +}", newCompilationUnit.ToFullString()); + } + + [FAQ(27)] + [Fact] + public void ReplaceSubExpression() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +class Program +{ + static void Main() + { + int i = 0, j = 0; + Console.WriteLine((i + j) - (i + j)); + } +}"); + CompilationUnitSyntax compilationUnit = (CompilationUnitSyntax)tree.GetRoot(); + + // Get BinaryExpressionSyntax corresponding to the two addition expressions 'i + j' above. + BinaryExpressionSyntax addExpression1 = compilationUnit.DescendantNodes() + .OfType().First(b => b.Kind() == SyntaxKind.AddExpression); + BinaryExpressionSyntax addExpression2 = compilationUnit.DescendantNodes() + .OfType().Last(b => b.Kind() == SyntaxKind.AddExpression); + + // Replace addition expressions 'i + j' with multiplication expressions 'i * j'. + BinaryExpressionSyntax multipyExpression1 = SyntaxFactory.BinaryExpression(SyntaxKind.MultiplyExpression, + addExpression1.Left, + SyntaxFactory.Token(SyntaxKind.AsteriskToken) + .WithLeadingTrivia(addExpression1.OperatorToken.LeadingTrivia) + .WithTrailingTrivia(addExpression1.OperatorToken.TrailingTrivia), + addExpression1.Right); + BinaryExpressionSyntax multipyExpression2 = SyntaxFactory.BinaryExpression(SyntaxKind.MultiplyExpression, + addExpression2.Left, + SyntaxFactory.Token(SyntaxKind.AsteriskToken) + .WithLeadingTrivia(addExpression2.OperatorToken.LeadingTrivia) + .WithTrailingTrivia(addExpression2.OperatorToken.TrailingTrivia), + addExpression2.Right); + + CompilationUnitSyntax newCompilationUnit = compilationUnit + .ReplaceNodes(nodes: new[] { addExpression1, addExpression2 }, + computeReplacementNode: + (originalNode, originalNodeWithReplacedDescendants) => + { + SyntaxNode newNode = null; + + if (originalNode == addExpression1) + { + newNode = multipyExpression1; + } + else if (originalNode == addExpression2) + { + newNode = multipyExpression2; + } + + return newNode; + }); + + Assert.Equal(@" +class Program +{ + static void Main() + { + int i = 0, j = 0; + Console.WriteLine((i * j) - (i * j)); + } +}", newCompilationUnit.ToFullString()); + } + + [FAQ(28)] + [Fact] + public void UseSymbolicInformationPlusRewriterToMakeCodeChanges() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +using System; +class Program +{ + static void Main() + { + C x = new C(); + C.ReferenceEquals(x, x); + } +} +class C +{ + C y = null; + public C() + { + y = new C(); + } +}"); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation") + .AddReferences(Mscorlib) + .AddSyntaxTrees(tree); + SemanticModel model = compilation.GetSemanticModel(tree); + + // Get the ClassDeclarationSyntax corresponding to 'class C' above. + ClassDeclarationSyntax classDeclaration = tree.GetRoot() + .DescendantNodes().OfType() + .Single(c => c.Identifier.ToString() == "C"); + + // Get Symbol corresponding to class C above. + INamedTypeSymbol searchSymbol = model.GetDeclaredSymbol(classDeclaration); + SyntaxNode oldRoot = tree.GetRoot(); + ClassRenamer rewriter = new ClassRenamer() + { + SearchSymbol = searchSymbol, + SemanticModel = model, + NewName = "C1" + }; + SyntaxNode newRoot = rewriter.Visit(oldRoot); + + Assert.Equal(@" +using System; +class Program +{ + static void Main() + { + C1 x = new C1(); + C1.ReferenceEquals(x, x); + } +} +class C1 +{ + C1 y = null; + public C1() + { + y = new C1(); + } +}", newRoot.ToFullString()); + } + + // Below CSharpSyntaxRewriter renames multiple occurrences of a particular class name under the SyntaxNode being visited. + // Note that the below rewriter is not a full / correct implementation of symbolic rename. For example, it doesn't + // handle destructors / aliases etc. A full implementation for symbolic rename would be more complicated and is + // beyond the scope of this sample. The intent of this sample is mainly to demonstrate how symbolic info can be used + // in conjunction a rewriter to make syntactic changes. + public class ClassRenamer : CSharpSyntaxRewriter + { + public ITypeSymbol SearchSymbol { get; set; } + public SemanticModel SemanticModel { get; set; } + public string NewName { get; set; } + + // Replace old ClassDeclarationSyntax with new one. + public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) + { + ClassDeclarationSyntax updatedClassDeclaration = (ClassDeclarationSyntax)base.VisitClassDeclaration(node); + + // Get TypeSymbol corresponding to the ClassDeclarationSyntax and check whether + // it is the same as the TypeSymbol we are searching for. + INamedTypeSymbol classSymbol = SemanticModel.GetDeclaredSymbol(node); + if (classSymbol.Equals(SearchSymbol)) + { + // Replace the identifier token containing the name of the class. + SyntaxToken updatedIdentifierToken = + SyntaxFactory.Identifier( + updatedClassDeclaration.Identifier.LeadingTrivia, + NewName, + updatedClassDeclaration.Identifier.TrailingTrivia); + + updatedClassDeclaration = updatedClassDeclaration.WithIdentifier(updatedIdentifierToken); + } + + return updatedClassDeclaration; + } + + // Replace old ConstructorDeclarationSyntax with new one. + public override SyntaxNode VisitConstructorDeclaration(ConstructorDeclarationSyntax node) + { + ConstructorDeclarationSyntax updatedConstructorDeclaration = (ConstructorDeclarationSyntax)base.VisitConstructorDeclaration(node); + + // Get TypeSymbol corresponding to the containing ClassDeclarationSyntax for the + // ConstructorDeclarationSyntax and check whether it is the same as the TypeSymbol + // we are searching for. + ITypeSymbol classSymbol = (ITypeSymbol)SemanticModel.GetDeclaredSymbol(node).ContainingSymbol; + if (classSymbol.Equals(SearchSymbol)) + { + // Replace the identifier token containing the name of the class. + SyntaxToken updatedIdentifierToken = + SyntaxFactory.Identifier( + updatedConstructorDeclaration.Identifier.LeadingTrivia, + NewName, + updatedConstructorDeclaration.Identifier.TrailingTrivia); + + updatedConstructorDeclaration = updatedConstructorDeclaration.WithIdentifier(updatedIdentifierToken); + } + + return updatedConstructorDeclaration; + } + + // Replace all occurrences of old class name with new one. + public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) + { + IdentifierNameSyntax updatedIdentifierName = (IdentifierNameSyntax)base.VisitIdentifierName(node); + + // Get TypeSymbol corresponding to the IdentifierNameSyntax and check whether + // it is the same as the TypeSymbol we are searching for. + ISymbol identifierSymbol = SemanticModel.GetSymbolInfo(node).Symbol; + + // Handle |C| x = new C(). + bool isMatchingTypeName = identifierSymbol.Equals(SearchSymbol); + + // Handle C x = new |C|(). + bool isMatchingConstructor = + identifierSymbol is IMethodSymbol && + ((IMethodSymbol)identifierSymbol).MethodKind == MethodKind.Constructor && + identifierSymbol.ContainingSymbol.Equals(SearchSymbol); + + if (isMatchingTypeName || isMatchingConstructor) + { + // Replace the identifier token containing the name of the class. + SyntaxToken updatedIdentifierToken = + SyntaxFactory.Identifier( + updatedIdentifierName.Identifier.LeadingTrivia, + NewName, + updatedIdentifierName.Identifier.TrailingTrivia); + + updatedIdentifierName = updatedIdentifierName.WithIdentifier(updatedIdentifierToken); + } + + return updatedIdentifierName; + } + } + + [FAQ(30)] + [Fact] + public void DeleteAssignmentStatementsFromASyntaxTree() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +class Program +{ + static void Main() + { + int x = 1; + x = 2; + if (true) + x = 3; + else x = 4; + } +}"); + SyntaxNode oldRoot = tree.GetRoot(); + AssignmentStatementRemover rewriter = new AssignmentStatementRemover(); + SyntaxNode newRoot = rewriter.Visit(oldRoot); + + Assert.Equal(@" +class Program +{ + static void Main() + { + int x = 1; + if (true) + ; + else ; + } +}", newRoot.ToFullString()); + } + + // Below CSharpSyntaxRewriter removes multiple assignment statements from under the SyntaxNode being visited. + public class AssignmentStatementRemover : CSharpSyntaxRewriter + { + public override SyntaxNode VisitExpressionStatement(ExpressionStatementSyntax node) + { + SyntaxNode updatedNode = base.VisitExpressionStatement(node); + + if (node.Expression.Kind() == SyntaxKind.SimpleAssignmentExpression) + { + if (node.Parent.Kind() == SyntaxKind.Block) + { + // There is a parent block so it is ok to remove the statement completely. + updatedNode = null; + } + else + { + // The parent context is some statement like an if statement without a block. + // Return an empty statement. + updatedNode = SyntaxFactory.EmptyStatement() + .WithLeadingTrivia(updatedNode.GetLeadingTrivia()) + .WithTrailingTrivia(updatedNode.GetTrailingTrivia()); + } + } + + return updatedNode; + } + } + + [FAQ(31)] + [Fact] + public void ConstructPointerOrArrayType() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +class Program +{ + static void Main() { } +}"); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation", + syntaxTrees: new[] { tree }, references: new[] { Mscorlib }); + + INamedTypeSymbol elementType = compilation.GetSpecialType(SpecialType.System_Int32); + + IPointerTypeSymbol pointerType = compilation.CreatePointerTypeSymbol(elementType); + Assert.Equal("int*", pointerType.ToDisplayString()); + + IArrayTypeSymbol arrayType = compilation.CreateArrayTypeSymbol(elementType, rank: 3); + Assert.Equal("int[*,*,*]", arrayType.ToDisplayString()); + } + + [FAQ(32)] + [Fact] + public void DeleteRegionsUsingRewriter() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +using System; +#region Program +class Program +{ + static void Main() + { + } +} +#endregion +#region Other +class C +{ +} +#endregion"); + SyntaxNode oldRoot = tree.GetRoot(); + + string expected = @" +using System; +class Program +{ + static void Main() + { + } +} +class C +{ +} +"; + CSharpSyntaxRewriter rewriter = new RegionRemover1(); + SyntaxNode newRoot = rewriter.Visit(oldRoot); + Assert.Equal(expected, newRoot.ToFullString()); + + rewriter = new RegionRemover2(); + newRoot = rewriter.Visit(oldRoot); + Assert.Equal(expected, newRoot.ToFullString()); + } + + // Below CSharpSyntaxRewriter removes all #regions and #endregions from under the SyntaxNode being visited. + public class RegionRemover1 : CSharpSyntaxRewriter + { + public override SyntaxTrivia VisitTrivia(SyntaxTrivia trivia) + { + SyntaxTrivia updatedTrivia = base.VisitTrivia(trivia); + if (trivia.Kind() == SyntaxKind.RegionDirectiveTrivia || + trivia.Kind() == SyntaxKind.EndRegionDirectiveTrivia) + { + // Remove the trivia entirely by returning default(SyntaxTrivia). + updatedTrivia = default(SyntaxTrivia); + } + + return updatedTrivia; + } + } + + // Below CSharpSyntaxRewriter removes all #regions and #endregions from under the SyntaxNode being visited. + public class RegionRemover2 : CSharpSyntaxRewriter + { + public override SyntaxToken VisitToken(SyntaxToken token) + { + // Remove all #regions and #endregions from underneath the token. + return token + .WithLeadingTrivia(RemoveRegions(token.LeadingTrivia)) + .WithTrailingTrivia(RemoveRegions(token.TrailingTrivia)); + } + + private SyntaxTriviaList RemoveRegions(SyntaxTriviaList oldTriviaList) + { + return SyntaxFactory.TriviaList(oldTriviaList + .Where(trivia => trivia.Kind() != SyntaxKind.RegionDirectiveTrivia && + trivia.Kind() != SyntaxKind.EndRegionDirectiveTrivia)); + } + } + + [FAQ(33)] + [Fact] + public void DeleteRegions() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +using System; +#region Program +class Program +{ + static void Main() + { + } +} +#endregion +#region Other +class C +{ +} +#endregion"); + SyntaxNode oldRoot = tree.GetRoot(); + + // Get all RegionDirective and EndRegionDirective trivia. + IEnumerable trivia = oldRoot.DescendantTrivia() + .Where(t => t.Kind() == SyntaxKind.RegionDirectiveTrivia || + t.Kind() == SyntaxKind.EndRegionDirectiveTrivia); + + SyntaxNode newRoot = oldRoot.ReplaceTrivia(trivia: trivia, + computeReplacementTrivia: + (originalTrivia, originalTriviaWithReplacedDescendants) => default(SyntaxTrivia)); + + Assert.Equal(@" +using System; +class Program +{ + static void Main() + { + } +} +class C +{ +} +", newRoot.ToFullString()); + } + + [FAQ(34)] + [Fact(Skip = "Need to load correct assembly references now that this is a .NET core app")] + public void InsertLoggingStatements() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@" +class Program +{ + static void Main() + { + System.Console.WriteLine(); + int total = 0; + for (int i=0; i < 5; ++i) + { + total += i; + } + if (true) total += 5; + } +}"); + SyntaxNode oldRoot = tree.GetRoot(); + ConsoleWriteLineInserter rewriter = new ConsoleWriteLineInserter(); + SyntaxNode newRoot = rewriter.Visit(oldRoot); + newRoot = newRoot.NormalizeWhitespace(); // fix up the whitespace so it is legible. + + SyntaxTree newTree = SyntaxFactory.SyntaxTree(newRoot, path: "MyCodeFile.cs", encoding: Encoding.UTF8); + CSharpCompilation compilation = CSharpCompilation.Create("MyCompilation") + .AddReferences(Mscorlib) + .AddSyntaxTrees(newTree); + + string output = Execute(compilation); + Assert.Equal(@" +0 +1 +3 +6 +10 +15 +", output); + } + + // Below CSharpSyntaxRewriter inserts a Console.WriteLine() statement to print the value of the + // LHS variable for compound assignment statements encountered in the input tree. + public class ConsoleWriteLineInserter : CSharpSyntaxRewriter + { + public override SyntaxNode VisitExpressionStatement(ExpressionStatementSyntax node) + { + SyntaxNode updatedNode = base.VisitExpressionStatement(node); + + if (node.Expression.Kind() == SyntaxKind.AddAssignmentExpression || + node.Expression.Kind() == SyntaxKind.SubtractAssignmentExpression || + node.Expression.Kind() == SyntaxKind.MultiplyAssignmentExpression || + node.Expression.Kind() == SyntaxKind.DivideAssignmentExpression) + { + // Print value of the variable on the 'Left' side of + // compound assignment statements encountered. + AssignmentExpressionSyntax compoundAssignmentExpression = (AssignmentExpressionSyntax)node.Expression; + StatementSyntax consoleWriteLineStatement = + SyntaxFactory.ParseStatement(string.Format("System.Console.WriteLine({0});", compoundAssignmentExpression.Left.ToString())); + + updatedNode = + SyntaxFactory.Block(SyntaxFactory.List( + new StatementSyntax[] + { + node.WithLeadingTrivia().WithTrailingTrivia(), // Remove leading and trailing trivia. + consoleWriteLineStatement + })) + .WithLeadingTrivia(node.GetLeadingTrivia()) // Attach leading trivia from original node. + .WithTrailingTrivia(node.GetTrailingTrivia()); // Attach trailing trivia from original node. + } + + return updatedNode; + } + } + + // A simple helper to execute the code present inside a compilation. + public string Execute(Compilation comp) + { + StringBuilder output = new StringBuilder(); + string exeFilename = "OutputCS.exe", pdbFilename = "OutputCS.pdb", xmlCommentsFilename = "OutputCS.xml"; + EmitResult emitResult = null; + + using (FileStream ilStream = new FileStream(exeFilename, FileMode.OpenOrCreate)) + { + using (FileStream pdbStream = new FileStream(pdbFilename, FileMode.OpenOrCreate)) + { + using (FileStream xmlCommentsStream = new FileStream(xmlCommentsFilename, FileMode.OpenOrCreate)) + { + // Emit IL, PDB and xml documentation comments for the compilation to disk. + emitResult = comp.Emit(ilStream, pdbStream, xmlCommentsStream); + } + } + } + + if (emitResult.Success) + { + Process p = Process.Start( + new ProcessStartInfo() + { + FileName = exeFilename, + UseShellExecute = false, + RedirectStandardOutput = true + }); + output.Append(p.StandardOutput.ReadToEnd()); + p.WaitForExit(); + } + else + { + output.AppendLine("Errors:"); + foreach (Diagnostic diag in emitResult.Diagnostics) + { + output.AppendLine(diag.ToString()); + } + } + + return output.ToString(); + } + + private class SimplifyNamesAnnotionRewriter : CSharpSyntaxRewriter + { + private SyntaxNode AnnotateNodeWithSimplifyAnnotation(SyntaxNode node) + { + return node.WithAdditionalAnnotations(Simplifier.Annotation); + } + + public override SyntaxNode VisitAliasQualifiedName(AliasQualifiedNameSyntax node) + { + // not descending into node to simplify the whole expression + return AnnotateNodeWithSimplifyAnnotation(node); + } + + public override SyntaxNode VisitQualifiedName(QualifiedNameSyntax node) + { + // not descending into node to simplify the whole expression + return AnnotateNodeWithSimplifyAnnotation(node); + } + + public override SyntaxNode VisitMemberAccessExpression(MemberAccessExpressionSyntax node) + { + // not descending into node to simplify the whole expression + return AnnotateNodeWithSimplifyAnnotation(node); + } + + public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) + { + // not descending into node to simplify the whole expression + return AnnotateNodeWithSimplifyAnnotation(node); + } + + public override SyntaxNode VisitGenericName(GenericNameSyntax node) + { + // not descending into node to simplify the whole expression + return AnnotateNodeWithSimplifyAnnotation(node); + } + } + + [FAQ(35)] + [Fact] + public void UseServices() + { + string source = @"using System.Diagnostics; +using System; +using System.IO; +namespace NS +{ +public class C +{ +} +} +class Program +{ + public static void Main() + { + System.Int32 i = 0; System.Console.WriteLine(i.ToString()); + Process p = Process.GetCurrentProcess(); + Console.WriteLine(p.Id); + } +}"; + ProjectId projectId = ProjectId.CreateNewId(); + DocumentId documentId = DocumentId.CreateNewId(projectId); + + Solution solution = new AdhocWorkspace().CurrentSolution + .AddProject(projectId, "MyProject", "MyProject", LanguageNames.CSharp) + .AddMetadataReference(projectId, Mscorlib) + .AddDocument(documentId, "MyFile.cs", source); + Document document = solution.GetDocument(documentId); + + // Format the document. + document = Formatter.FormatAsync(document).Result; + string expected = @"using System.Diagnostics; +using System; +using System.IO; +namespace NS +{ + public class C + { + } +} +class Program +{ + public static void Main() + { + System.Int32 i = 0; System.Console.WriteLine(i.ToString()); + Process p = Process.GetCurrentProcess(); + Console.WriteLine(p.Id); + } +}"; + string actual = document.GetSyntaxRootAsync().Result.ToString(); + Assert.Equal(expected, actual); + + // Simplify names used in the document i.e. remove unnecessary namespace qualifiers. + SyntaxNode newRoot = (SyntaxNode)document.GetSyntaxRootAsync().Result; + newRoot = new SimplifyNamesAnnotionRewriter().Visit(newRoot); + document = document.WithSyntaxRoot(newRoot); + + document = Simplifier.ReduceAsync(document).Result; + expected = @"using System.Diagnostics; +using System; +using System.IO; +namespace NS +{ + public class C + { + } +} +class Program +{ + public static void Main() + { + int i = 0; System.Console.WriteLine(i.ToString()); + Process p = Process.GetCurrentProcess(); + Console.WriteLine(p.Id); + } +}"; + actual = document.GetSyntaxRootAsync().Result.ToString(); + Assert.Equal(expected, actual); + } + #endregion + } +} diff --git a/samples/CSharp/APISamples/Parsing.cs b/samples/CSharp/APISamples/Parsing.cs new file mode 100644 index 000000000..b2c49a5d2 --- /dev/null +++ b/samples/CSharp/APISamples/Parsing.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using Xunit; + +namespace APISamples +{ + public class Parsing + { + [Fact] + public void TextParseTreeRoundtrip() + { + string text = "class C { void M() { } } // exact text round trip, including comments and whitespace"; + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(text); + Assert.Equal(text, tree.ToString()); + } + + [Fact] + public void DetermineValidIdentifierName() + { + ValidIdentifier("@class", true); + ValidIdentifier("class", false); + } + + private void ValidIdentifier(string identifier, bool expectedValid) + { + SyntaxToken token = SyntaxFactory.ParseToken(identifier); + Assert.Equal(expectedValid, + token.Kind() == SyntaxKind.IdentifierToken && token.Span.Length == identifier.Length); + } + + [Fact] + public void SyntaxFactsMethods() + { + Assert.Equal("protected internal", SyntaxFacts.GetText(Accessibility.ProtectedOrInternal)); + Assert.Equal("private protected", SyntaxFacts.GetText(Accessibility.ProtectedAndInternal)); + Assert.Equal("??", SyntaxFacts.GetText(SyntaxKind.QuestionQuestionToken)); + Assert.Equal("this", SyntaxFacts.GetText(SyntaxKind.ThisKeyword)); + + Assert.Equal(SyntaxKind.CharacterLiteralExpression, SyntaxFacts.GetLiteralExpression(SyntaxKind.CharacterLiteralToken)); + Assert.Equal(SyntaxKind.CoalesceExpression, SyntaxFacts.GetBinaryExpression(SyntaxKind.QuestionQuestionToken)); + Assert.Equal(SyntaxKind.None, SyntaxFacts.GetBinaryExpression(SyntaxKind.UndefDirectiveTrivia)); + Assert.False(SyntaxFacts.IsPunctuation(SyntaxKind.StringLiteralToken)); + } + + [Fact] + public void ParseTokens() + { + IEnumerable tokens = SyntaxFactory.ParseTokens("class C { // trivia"); + IEnumerable fullTexts = tokens.Select(token => token.ToFullString()); + + Assert.True(fullTexts.SequenceEqual(new[] + { + "class ", + "C ", + "{ // trivia", + "" // EOF + })); + } + + [Fact] + public void ParseExpression() + { + ExpressionSyntax expression = SyntaxFactory.ParseExpression("1 + 2"); + if (expression.Kind() == SyntaxKind.AddExpression) + { + BinaryExpressionSyntax binaryExpression = (BinaryExpressionSyntax)expression; + SyntaxToken operatorToken = binaryExpression.OperatorToken; + Assert.Equal("+", operatorToken.ToString()); + + ExpressionSyntax left = binaryExpression.Left; + Assert.Equal(SyntaxKind.NumericLiteralExpression, left.Kind()); + } + } + + [Fact] + public void IncrementalParse() + { + SourceText oldText = SourceText.From("class C { }"); + SourceText newText = oldText.WithChanges(new TextChange(new TextSpan(9, 0), "void M() { } ")); + + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(oldText); + + SyntaxTree newTree = tree.WithChangedText(newText); + + Assert.Equal(newText.ToString(), newTree.ToString()); + } + + [Fact] + public void PreprocessorDirectives() + { + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(@"#if true +class A { } +#else +class B { } +#endif"); + SyntaxToken eof = tree.GetRoot().FindToken(tree.GetText().Length, false); + Assert.True(eof.HasLeadingTrivia); + Assert.False(eof.HasTrailingTrivia); + Assert.True(eof.ContainsDirectives); + + SyntaxTriviaList trivia = eof.LeadingTrivia; + Assert.Equal(3, trivia.Count); + Assert.Equal("#else", trivia.ElementAt(0).ToString()); + Assert.Equal(SyntaxKind.DisabledTextTrivia, trivia.ElementAt(1).Kind()); + Assert.Equal("#endif", trivia.ElementAt(2).ToString()); + + DirectiveTriviaSyntax directive = tree.GetRoot().GetLastDirective(); + Assert.Equal("endif", directive.DirectiveNameToken.Value); + + directive = directive.GetPreviousDirective(); + Assert.Equal("else", directive.DirectiveNameToken.Value); + } + } +} diff --git a/samples/CSharp/APISamples/SymbolsAndSemantics.cs b/samples/CSharp/APISamples/SymbolsAndSemantics.cs new file mode 100644 index 000000000..3a20db702 --- /dev/null +++ b/samples/CSharp/APISamples/SymbolsAndSemantics.cs @@ -0,0 +1,346 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using Xunit; + +namespace APISamples +{ + public class SymbolsAndSemantics + { + [Fact] + public void GetExpressionType() + { + TestCode testCode = new TestCode(@"class Program +{ + public static void Method() + { + var local = new Program().ToString() + string.Empty; + } +}"); + + TypeSyntax varNode = testCode.SyntaxTree.GetRoot() + .DescendantNodes() + .OfType() + .First() + .Declaration + .Type; + + TypeInfo semanticInfo = testCode.SemanticModel.GetTypeInfo(varNode); + Assert.Equal("String", semanticInfo.Type.Name); + } + + [Fact] + public void BindNameToSymbol() + { + TestCode testCode = new TestCode("using System;"); + CompilationUnitSyntax compilationUnit = testCode.SyntaxTree.GetRoot() as CompilationUnitSyntax; + NameSyntax node = compilationUnit.Usings[0].Name; + + SymbolInfo semanticInfo = testCode.SemanticModel.GetSymbolInfo(node); + INamespaceSymbol namespaceSymbol = semanticInfo.Symbol as INamespaceSymbol; + + Assert.Contains(namespaceSymbol.GetNamespaceMembers(), symbol => symbol.Name == "Collections"); + } + + [Fact] + public void GetDeclaredSymbol() + { + TestCode testCode = new TestCode("namespace Acme { internal class C$lass1 { } }"); + INamedTypeSymbol symbol = testCode.SemanticModel.GetDeclaredSymbol((TypeDeclarationSyntax)testCode.SyntaxNode); + + Assert.True(symbol.CanBeReferencedByName); + Assert.Equal("Acme", symbol.ContainingNamespace.Name); + Assert.Equal(Accessibility.Internal, symbol.DeclaredAccessibility); + Assert.Equal(SymbolKind.NamedType, symbol.Kind); + Assert.Equal("Class1", symbol.Name); + Assert.Equal("Acme.Class1", symbol.ToDisplayString()); + Assert.Equal("Acme.Class1", symbol.ToString()); + } + + [Fact] + public void GetSymbolXmlDocComments() + { + TestCode testCode = new TestCode(@" +/// +/// This is a test class! +/// +class C$lass1 { }"); + INamedTypeSymbol symbol = testCode.SemanticModel.GetDeclaredSymbol((TypeDeclarationSyntax)testCode.SyntaxNode); + + string actualXml = symbol.GetDocumentationCommentXml(); + string expectedXml = +@" + + This is a test class! + + +"; + Assert.Equal(expectedXml, actualXml); + } + + [Fact] + public void SymbolDisplayFormatTest() + { + TestCode testCode = new TestCode(@" +class C1 { } +class C2 { + public static TSource M(this C1 source, // comment here +int index) {} }"); + + SymbolDisplayFormat format = new SymbolDisplayFormat( + extensionMethodStyle: SymbolDisplayExtensionMethodStyle.StaticMethod, + genericsOptions: + SymbolDisplayGenericsOptions.IncludeTypeParameters | + SymbolDisplayGenericsOptions.IncludeVariance, + memberOptions: + SymbolDisplayMemberOptions.IncludeParameters | + SymbolDisplayMemberOptions.IncludeModifiers | + SymbolDisplayMemberOptions.IncludeAccessibility | + SymbolDisplayMemberOptions.IncludeType | + SymbolDisplayMemberOptions.IncludeContainingType, + parameterOptions: + SymbolDisplayParameterOptions.IncludeExtensionThis | + SymbolDisplayParameterOptions.IncludeType | + SymbolDisplayParameterOptions.IncludeName | + SymbolDisplayParameterOptions.IncludeDefaultValue, + miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes); + + ISymbol symbol = testCode.Compilation + .SourceModule + .GlobalNamespace + .GetTypeMembers("C2")[0] + .GetMembers("M")[0]; + + Assert.Equal("public static TSource C2.M(this C1 source, int index)", symbol.ToDisplayString(format)); + } + + [Fact] + public void EnumerateSymbolsInCompilation() + { + string file1 = "public class Animal { public virtual void MakeSound() { } }"; + string file2 = "class Cat : Animal { public override void MakeSound() { } }"; + CSharpCompilation compilation = CSharpCompilation.Create("test") + .AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(file1), SyntaxFactory.ParseSyntaxTree(file2)) + .AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location)); + + INamespaceSymbol globalNamespace = compilation.SourceModule.GlobalNamespace; + + StringBuilder sb = new StringBuilder(); + EnumSymbols(globalNamespace, symbol => sb.AppendLine(symbol.ToString())); + + Assert.Equal(@" +Animal +Animal.MakeSound() +Animal.Animal() +Cat +Cat.MakeSound() +Cat.Cat() +", sb.ToString()); + } + + private void EnumSymbols(ISymbol symbol, Action callback) + { + callback(symbol); + foreach (ISymbol childSymbol in GetMembers(symbol)) + { + EnumSymbols(childSymbol, callback); + } + } + + private IEnumerable GetMembers(ISymbol parent) + { + if (parent is INamespaceOrTypeSymbol container) + { + return container.GetMembers().AsEnumerable(); + } + + return Enumerable.Empty(); + } + + [Fact] + public void AnalyzeRegionControlFlow() + { + TestCode testCode = new TestCode(@" +class C { + public void F() + { + goto L1; // 1 +/*start*/ + L1: ; + if (false) return; +/*end*/ + goto L1; // 2 + } +}"); + testCode.GetStatementsBetweenMarkers(out StatementSyntax firstStatement, out StatementSyntax lastStatement); + ControlFlowAnalysis regionControlFlowAnalysis = + testCode.SemanticModel.AnalyzeControlFlow(firstStatement, lastStatement); + + Assert.Single(regionControlFlowAnalysis.EntryPoints); + Assert.Single(regionControlFlowAnalysis.ExitPoints); + Assert.True(regionControlFlowAnalysis.EndPointIsReachable); + + BlockSyntax methodBody = testCode.SyntaxTree + .GetRoot() + .DescendantNodes() + .OfType() + .First() + .Body; + + regionControlFlowAnalysis = testCode.SemanticModel.AnalyzeControlFlow(methodBody, methodBody); + + Assert.False(regionControlFlowAnalysis.EndPointIsReachable); + } + + [Fact] + public void AnalyzeRegionDataFlow() + { + TestCode testCode = new TestCode(@" +class C { + public void F(int x) + { + int a; +/*start*/ + int b; + int x, y = 1; + { var z = ""a""; } +/*end*/ + int c; + } +}"); + testCode.GetStatementsBetweenMarkers(out StatementSyntax firstStatement, out StatementSyntax lastStatement); + DataFlowAnalysis regionDataFlowAnalysis = testCode.SemanticModel.AnalyzeDataFlow(firstStatement, lastStatement); + + Assert.Equal("b,x,y,z", string.Join(",", regionDataFlowAnalysis + .VariablesDeclared + .Select(symbol => symbol.Name))); + } + + [Fact] + public void FailedOverloadResolution() + { + TestCode testCode = new TestCode(@" +class Program +{ + static void Main(string[] args) + { + int i = 8; + int j = i + q; + X$.f(""hello""); + } +} + +class X +{ + public static void f() { } + public static void f(int i) { } +} +"); + TypeInfo typeInfo = testCode.SemanticModel.GetTypeInfo((ExpressionSyntax)testCode.SyntaxNode); + SymbolInfo semanticInfo = testCode.SemanticModel.GetSymbolInfo((ExpressionSyntax)testCode.SyntaxNode); + + Assert.Null(typeInfo.Type); + Assert.Null(typeInfo.ConvertedType); + + Assert.Null(semanticInfo.Symbol); + Assert.Equal(CandidateReason.OverloadResolutionFailure, semanticInfo.CandidateReason); + Assert.Equal(2, semanticInfo.CandidateSymbols.Length); + ISymbol[] sortedCandidates = semanticInfo.CandidateSymbols.AsEnumerable().OrderBy(s => s.ToDisplayString()).ToArray(); + Assert.Equal("X.f()", sortedCandidates[0].ToDisplayString()); + Assert.Equal(SymbolKind.Method, sortedCandidates[0].Kind); + Assert.Equal("X.f(int)", sortedCandidates[1].ToDisplayString()); + Assert.Equal(SymbolKind.Method, sortedCandidates[1].Kind); + + System.Collections.Immutable.ImmutableArray memberGroup = testCode.SemanticModel.GetMemberGroup((ExpressionSyntax)testCode.SyntaxNode); + + Assert.Equal(2, memberGroup.Length); + ISymbol[] sortedMemberGroup = memberGroup.AsEnumerable().OrderBy(s => s.ToDisplayString()).ToArray(); + Assert.Equal("X.f()", sortedMemberGroup[0].ToDisplayString()); + Assert.Equal("X.f(int)", sortedMemberGroup[1].ToDisplayString()); + } + + /// + /// Helper class to bundle together information about a piece of analyzed test code. + /// + private class TestCode + { + public int Position { get; private set; } + public string Text { get; private set; } + + public SyntaxTree SyntaxTree { get; private set; } + + public SyntaxToken Token { get; private set; } + public SyntaxNode SyntaxNode { get; private set; } + + public Compilation Compilation { get; private set; } + public SemanticModel SemanticModel { get; private set; } + + public TestCode(string textWithMarker) + { + // $ marks the position in source code. It's better than passing a manually calculated + // int, and is just for test convenience. $ is a char that is used nowhere in the C# + // language. + Position = textWithMarker.IndexOf('$'); + if (Position != -1) + { + textWithMarker = textWithMarker.Remove(Position, 1); + } + + Text = textWithMarker; + SyntaxTree = SyntaxFactory.ParseSyntaxTree(Text); + + if (Position != -1) + { + Token = SyntaxTree.GetRoot().FindToken(Position); + SyntaxNode = Token.Parent; + } + + Compilation = CSharpCompilation + .Create("test") + .AddSyntaxTrees(SyntaxTree) + .AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location)); + + SemanticModel = Compilation.GetSemanticModel(SyntaxTree); + } + + public void GetStatementsBetweenMarkers(out StatementSyntax firstStatement, out StatementSyntax lastStatement) + { + TextSpan span = GetSpanBetweenMarkers(); + IEnumerable statementsInside = SyntaxTree + .GetRoot() + .DescendantNodes(span) + .OfType() + .Where(s => span.Contains(s.Span)); + StatementSyntax first = firstStatement = statementsInside + .First(); + lastStatement = statementsInside + .Where(s => s.Parent == first.Parent) + .Last(); + } + + public TextSpan GetSpanBetweenMarkers() + { + SyntaxTrivia startComment = SyntaxTree + .GetRoot() + .DescendantTrivia() + .First(syntaxTrivia => syntaxTrivia.ToString().Contains("start")); + SyntaxTrivia endComment = SyntaxTree + .GetRoot() + .DescendantTrivia() + .First(syntaxTrivia => syntaxTrivia.ToString().Contains("end")); + + TextSpan textSpan = TextSpan.FromBounds( + startComment.FullSpan.End, + endComment.FullSpan.Start); + return textSpan; + } + } + } +} diff --git a/samples/CSharp/APISamples/SyntaxTrees.cs b/samples/CSharp/APISamples/SyntaxTrees.cs new file mode 100644 index 000000000..9841272d2 --- /dev/null +++ b/samples/CSharp/APISamples/SyntaxTrees.cs @@ -0,0 +1,172 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Xunit; + +namespace APISamples +{ + public class SyntaxTrees + { + [Fact] + public void FindNodeUsingMembers() + { + string text = "class C { void M(int i) { } }"; + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(text); + CompilationUnitSyntax compilationUnit = (CompilationUnitSyntax)tree.GetRoot(); + TypeDeclarationSyntax typeDeclaration = (TypeDeclarationSyntax)compilationUnit.Members[0]; + MethodDeclarationSyntax methodDeclaration = (MethodDeclarationSyntax)typeDeclaration.Members[0]; + ParameterSyntax parameter = methodDeclaration.ParameterList.Parameters[0]; + SyntaxToken parameterName = parameter.Identifier; + Assert.Equal("i", parameterName.ValueText); + } + + [Fact] + public void FindNodeUsingQuery() + { + string text = "class C { void M(int i) { } }"; + SyntaxNode root = SyntaxFactory.ParseCompilationUnit(text); + ParameterSyntax parameterDeclaration = root + .DescendantNodes() + .OfType() + .First(); + Assert.Equal("i", parameterDeclaration.Identifier.ValueText); + } + + [Fact] + public void UpdateNode() + { + string text = "class C { void M() { } }"; + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(text); + CompilationUnitSyntax root = (CompilationUnitSyntax)tree.GetRoot(); + MethodDeclarationSyntax method = root + .DescendantNodes() + .OfType() + .First(); + MethodDeclarationSyntax newMethod = method.Update( + method.AttributeLists, + method.Modifiers, + method.ReturnType, + method.ExplicitInterfaceSpecifier, + SyntaxFactory.Identifier("NewMethodName"), + method.TypeParameterList, + method.ParameterList, + method.ConstraintClauses, + method.Body, + method.ExpressionBody, + method.SemicolonToken); + + root = root.ReplaceNode(method, newMethod); + tree = tree.WithRootAndOptions(root, tree.Options); + Assert.Equal("class C { void NewMethodName() { } }", tree.GetText().ToString()); + } + + [Fact] + public void InsertNode() + { + string text = "class C { void M() { } }"; + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(text); + CompilationUnitSyntax root = (CompilationUnitSyntax)tree.GetRoot(); + ClassDeclarationSyntax classNode = root.ChildNodes().First() as ClassDeclarationSyntax; + + MethodDeclarationSyntax newMethod = SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName("int"), SyntaxFactory.Identifier("NewMethod")) + .WithBody(SyntaxFactory.Block()); + + SyntaxList newMembers = SyntaxFactory.List(classNode.Members.Concat(new[] { newMethod })); + + ClassDeclarationSyntax newClass = SyntaxFactory.ClassDeclaration( + classNode.AttributeLists, + classNode.Modifiers, + classNode.Keyword, + classNode.Identifier, + classNode.TypeParameterList, + classNode.BaseList, + classNode.ConstraintClauses, + classNode.OpenBraceToken, + newMembers, + classNode.CloseBraceToken, + classNode.SemicolonToken).NormalizeWhitespace(elasticTrivia: true); + + root = root.ReplaceNode(classNode, newClass); + tree = tree.WithRootAndOptions(root, tree.Options); + Assert.Equal(@"class C +{ + void M() + { + } + + int NewMethod() + { + } +}", tree.GetText().ToString()); + } + + [Fact] + public void WalkTreeUsingSyntaxWalker() + { + string text = "class Class { void Method1() { } struct S { } void Method2() { } }"; + SyntaxNode node = SyntaxFactory.ParseCompilationUnit(text); + FileContentsDumper visitor = new FileContentsDumper(); + visitor.Visit(node); + Assert.Equal(@"class Class + Method1 +struct S + Method2 +", visitor.ToString()); + } + + [Fact] + public void TransformTreeUsingSyntaxRewriter() + { + string text = "class C { void M() { } int field; }"; + SyntaxTree tree = SyntaxFactory.ParseSyntaxTree(text); + SyntaxNode newRoot = new RemoveMethodsRewriter().Visit(tree.GetRoot()); + Assert.Equal("class C { int field; }", newRoot.ToFullString()); + } + + private class RemoveMethodsRewriter : CSharpSyntaxRewriter + { + public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) + { + return null; + } + } + + private class FileContentsDumper : CSharpSyntaxWalker + { + private readonly StringBuilder sb = new StringBuilder(); + + public override void VisitClassDeclaration(ClassDeclarationSyntax node) + { + sb.AppendLine(node.Keyword.ValueText + " " + node.Identifier.ValueText); + base.VisitClassDeclaration(node); + } + + public override void VisitStructDeclaration(StructDeclarationSyntax node) + { + sb.AppendLine(node.Keyword.ValueText + " " + node.Identifier.ValueText); + base.VisitStructDeclaration(node); + } + + public override void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node) + { + sb.AppendLine(node.Keyword.ValueText + " " + node.Identifier.ValueText); + base.VisitInterfaceDeclaration(node); + } + + public override void VisitMethodDeclaration(MethodDeclarationSyntax node) + { + sb.AppendLine(" " + node.Identifier.ToString()); + base.VisitMethodDeclaration(node); + } + + public override string ToString() + { + return sb.ToString(); + } + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Implementation/AdditionalFileAnalyzers/SimpleAdditionalFileAnalyzer.cs b/samples/CSharp/Analyzers/Analyzers.Implementation/AdditionalFileAnalyzers/SimpleAdditionalFileAnalyzer.cs new file mode 100644 index 000000000..d671133e9 --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Implementation/AdditionalFileAnalyzers/SimpleAdditionalFileAnalyzer.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; + +namespace Sample.Analyzers +{ + /// + /// Analyzer to demonstrate reading an additional file line-by-line. + /// It looks for an additional file named "Terms.txt" and extracts a set of + /// terms, one per line. It then detects type names that use those terms and + /// reports diagnostics. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + class SimpleAdditionalFileAnalyzer : DiagnosticAnalyzer + { + private const string Title = "Type name contains invalid term"; + private const string MessageFormat = "The term '{0}' is not allowed in a type name."; + + private static readonly DiagnosticDescriptor Rule = + new DiagnosticDescriptor( + DiagnosticIds.SimpleAdditionalFileAnalyzerRuleId, + Title, + MessageFormat, + DiagnosticCategories.AdditionalFile, + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(compilationStartContext => + { + // Find the additional file with the terms. + ImmutableArray additionalFiles = compilationStartContext.Options.AdditionalFiles; + AdditionalText termsFile = additionalFiles.FirstOrDefault(file => Path.GetFileName(file.Path).Equals("Terms.txt")); + + if (termsFile != null) + { + HashSet terms = new HashSet(); + + // Read the file line-by-line to get the terms. + SourceText fileText = termsFile.GetText(compilationStartContext.CancellationToken); + foreach (TextLine line in fileText.Lines) + { + terms.Add(line.ToString()); + } + + // Check every named type for the invalid terms. + compilationStartContext.RegisterSymbolAction(symbolAnalysisContext => + { + INamedTypeSymbol namedTypeSymbol = (INamedTypeSymbol)symbolAnalysisContext.Symbol; + string symbolName = namedTypeSymbol.Name; + + foreach (string term in terms) + { + if (symbolName.Contains(term)) + { + symbolAnalysisContext.ReportDiagnostic( + Diagnostic.Create(Rule, namedTypeSymbol.Locations[0], term)); + } + } + }, + SymbolKind.NamedType); + } + }); + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Implementation/AdditionalFileAnalyzers/XmlAdditionalFileAnalyzer.cs b/samples/CSharp/Analyzers/Analyzers.Implementation/AdditionalFileAnalyzers/XmlAdditionalFileAnalyzer.cs new file mode 100644 index 000000000..cf2b5980a --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Implementation/AdditionalFileAnalyzers/XmlAdditionalFileAnalyzer.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; + +namespace Sample.Analyzers +{ + /// + /// Analyzer to demonstrate reading an additional file with a structured format. + /// It looks for an additional file named "Terms.xml" and dumps it to a stream + /// so that it can be loaded into an . It then extracts + /// terms from the XML, detects type names that use those terms and reports + /// diagnostics on them. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + class XmlAdditionalFileAnalyzer : DiagnosticAnalyzer + { + private const string Title = "Type name contains invalid term"; + private const string MessageFormat = "The term '{0}' is not allowed in a type name."; + + private static readonly DiagnosticDescriptor Rule = + new DiagnosticDescriptor( + DiagnosticIds.XmlAdditionalFileAnalyzerRuleId, + Title, + MessageFormat, + DiagnosticCategories.AdditionalFile, + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(compilationStartContext => + { + // Find the additional file with the terms. + ImmutableArray additionalFiles = compilationStartContext.Options.AdditionalFiles; + AdditionalText termsFile = additionalFiles.FirstOrDefault(file => Path.GetFileName(file.Path).Equals("Terms.xml")); + + if (termsFile != null) + { + HashSet terms = new HashSet(); + SourceText fileText = termsFile.GetText(compilationStartContext.CancellationToken); + + // Write the additional file back to a stream. + MemoryStream stream = new MemoryStream(); + using (StreamWriter writer = new StreamWriter(stream)) + { + fileText.Write(writer); + } + + // Read all the elements to get the terms. + XDocument document = XDocument.Load(stream); + foreach (XElement termElement in document.Descendants("Term")) + { + terms.Add(termElement.Value); + } + + // Check every named type for the invalid terms. + compilationStartContext.RegisterSymbolAction(symbolAnalysisContext => + { + INamedTypeSymbol namedTypeSymbol = (INamedTypeSymbol)symbolAnalysisContext.Symbol; + string symbolName = namedTypeSymbol.Name; + + foreach (string term in terms) + { + if (symbolName.Contains(term)) + { + symbolAnalysisContext.ReportDiagnostic( + Diagnostic.Create(Rule, namedTypeSymbol.Locations[0], term)); + } + } + }, + SymbolKind.NamedType); + } + }); + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Implementation/Analyzers.CSharp.csproj b/samples/CSharp/Analyzers/Analyzers.Implementation/Analyzers.CSharp.csproj new file mode 100644 index 000000000..64fca041f --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Implementation/Analyzers.CSharp.csproj @@ -0,0 +1,32 @@ + + + + netstandard1.3 + false + True + + + + Sample.Analyzers + 0.0.1 + Microsoft + https://github.com/dotnet/roslyn-sdk + false + Analyzers + A set of sample analyzers written in C#. + Copyright + sample, analyzers + true + + + + + + + + + + + + + diff --git a/samples/CSharp/Analyzers/Analyzers.Implementation/DiagnosticCategories.cs b/samples/CSharp/Analyzers/Analyzers.Implementation/DiagnosticCategories.cs new file mode 100644 index 000000000..d11061304 --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Implementation/DiagnosticCategories.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Sample.Analyzers +{ + public static class DiagnosticCategories + { + public const string Stateless = "SampleStatelessAnalyzers"; + public const string Stateful = "SampleStatefulAnalyzers"; + public const string AdditionalFile = "SampleAdditionalFileAnalyzers"; + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Implementation/DiagnosticIds.cs b/samples/CSharp/Analyzers/Analyzers.Implementation/DiagnosticIds.cs new file mode 100644 index 000000000..8970a77b5 --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Implementation/DiagnosticIds.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Sample.Analyzers +{ + public static class DiagnosticIds + { + // Stateless analyzer IDs. + public const string SymbolAnalyzerRuleId = "CSS0001"; + public const string SyntaxNodeAnalyzerRuleId = "CSS0002"; + public const string SyntaxTreeAnalyzerRuleId = "CSS0003"; + public const string SemanticModelAnalyzerRuleId = "CSS0004"; + public const string CodeBlockAnalyzerRuleId = "CSS0005"; + public const string CompilationAnalyzerRuleId = "CSS0006"; + public const string IOperationAnalyzerRuleId = "CSS0007"; + + // Stateful analyzer IDs. + public const string CodeBlockStartedAnalyzerRuleId = "CSS0101"; + public const string CompilationStartedAnalyzerRuleId = "CSS0102"; + public const string CompilationStartedAnalyzerWithCompilationWideAnalysisRuleId = "CSS0103"; + + // Additional File analyzer IDs. + public const string SimpleAdditionalFileAnalyzerRuleId = "CSS0201"; + public const string XmlAdditionalFileAnalyzerRuleId = "CSS0202"; + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Implementation/StatefulAnalyzers/CodeBlockStartedAnalyzer.cs b/samples/CSharp/Analyzers/Analyzers.Implementation/StatefulAnalyzers/CodeBlockStartedAnalyzer.cs new file mode 100644 index 000000000..5b19fc377 --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Implementation/StatefulAnalyzers/CodeBlockStartedAnalyzer.cs @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Sample.Analyzers +{ + /// + /// Analyzer to demonstrate code block wide analysis. + /// It computes and reports diagnostics for unused parameters in methods. + /// It performs code block wide analysis to detect such unused parameters and reports diagnostics for them in the code block end action. + /// + /// The analyzer registers: + /// (a) A code block start action, which initializes per-code block mutable state. We mark all parameters as unused at start of analysis. + /// (b) A code block syntax node action, which identifes parameter references and marks the corresponding parameter as used. + /// (c) A code block end action, which reports diagnostics based on the final state, for all parameters which are unused. + /// + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class CodeBlockStartedAnalyzer : DiagnosticAnalyzer + { + private const string Title = "Remove unused parameters"; + public const string MessageFormat = "Parameter '{0}' is unused in the method '{1}'."; + private const string Description = "Remove unused parameters."; + + internal static DiagnosticDescriptor Rule = + new DiagnosticDescriptor( + DiagnosticIds.CodeBlockStartedAnalyzerRuleId, + Title, + MessageFormat, + DiagnosticCategories.Stateful, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: Description); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCodeBlockStartAction(startCodeBlockContext => + { + // We only care about method bodies. + if (startCodeBlockContext.OwningSymbol.Kind != SymbolKind.Method) + { + return; + } + + // We only care about methods with parameters. + IMethodSymbol method = (IMethodSymbol)startCodeBlockContext.OwningSymbol; + if (method.Parameters.IsEmpty) + { + return; + } + + // Initialize local mutable state in the start action. + UnusedParametersAnalyzer analyzer = new UnusedParametersAnalyzer(method); + + // Register an intermediate non-end action that accesses and modifies the state. + startCodeBlockContext.RegisterSyntaxNodeAction(analyzer.AnalyzeSyntaxNode, SyntaxKind.IdentifierName); + + // Register an end action to report diagnostics based on the final state. + startCodeBlockContext.RegisterCodeBlockEndAction(analyzer.CodeBlockEndAction); + }); + } + + private class UnusedParametersAnalyzer + { + #region Per-CodeBlock mutable state + + private readonly HashSet _unusedParameters; + private readonly HashSet _unusedParameterNames; + + #endregion + + #region State intialization + + public UnusedParametersAnalyzer(IMethodSymbol method) + { + // Initialization: Assume all parameters are unused. + IEnumerable parameters = method.Parameters.Where(p => !p.IsImplicitlyDeclared && p.Locations.Length > 0); + _unusedParameters = new HashSet(parameters); + _unusedParameterNames = new HashSet(parameters.Select(p => p.Name)); + } + + #endregion + + #region Intermediate actions + + public void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) + { + // Check if we have any pending unreferenced parameters. + if (_unusedParameters.Count == 0) + { + return; + } + + // Syntactic check to avoid invoking GetSymbolInfo for every identifier. + IdentifierNameSyntax identifier = (IdentifierNameSyntax)context.Node; + if (!_unusedParameterNames.Contains(identifier.Identifier.ValueText)) + { + return; + } + + // Mark parameter as used. + if (context.SemanticModel.GetSymbolInfo(identifier, context.CancellationToken).Symbol is IParameterSymbol parmeter && + _unusedParameters.Contains(parmeter)) + { + _unusedParameters.Remove(parmeter); + _unusedParameterNames.Remove(parmeter.Name); + } + } + + #endregion + + #region End action + + public void CodeBlockEndAction(CodeBlockAnalysisContext context) + { + // Report diagnostics for unused parameters. + foreach (IParameterSymbol parameter in _unusedParameters) + { + Diagnostic diagnostic = Diagnostic.Create(Rule, parameter.Locations[0], parameter.Name, parameter.ContainingSymbol.Name); + context.ReportDiagnostic(diagnostic); + } + } + + #endregion + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Implementation/StatefulAnalyzers/CompilationStartedAnalyzer.cs b/samples/CSharp/Analyzers/Analyzers.Implementation/StatefulAnalyzers/CompilationStartedAnalyzer.cs new file mode 100644 index 000000000..bf9ae560d --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Implementation/StatefulAnalyzers/CompilationStartedAnalyzer.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Sample.Analyzers +{ + /// + /// Analyzer to demonstrate analysis within a compilation defining certain well-known symbol(s). + /// It computes and reports diagnostics for all public implementations of an interface, which is only supposed to be implemented internally. + /// + /// The analyzer registers: + /// (a) A compilation start action, which initializes per-compilation immutable state. We fetch and store the type symbol for the interface type in the compilation. + /// (b) A compilation symbol action, which identifies all named types implementing this interface, and reports diagnostics for all but internal allowed well known types. + /// + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class CompilationStartedAnalyzer : DiagnosticAnalyzer + { + private const string Title = "Do not implement unsupported interface"; + public const string MessageFormat = "Type '{0}' implements interface '{1}', which is only meant for internal implementation and might change in future. You should avoid implementing this interface."; + private const string Description = "Do not implement unsupported interface."; + + internal static DiagnosticDescriptor Rule = + new DiagnosticDescriptor( + DiagnosticIds.CompilationStartedAnalyzerRuleId, + Title, + MessageFormat, + DiagnosticCategories.Stateful, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: Description); + + public const string DontInheritInterfaceTypeName = "MyInterfaces.Interface"; + public const string AllowedInternalImplementationTypeName = "MyInterfaces.MyInterfaceImpl"; + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(compilationContext => + { + // We only care about compilations where interface type "DontInheritInterfaceTypeName" is available. + INamedTypeSymbol interfaceType = compilationContext.Compilation.GetTypeByMetadataName(DontInheritInterfaceTypeName); + if (interfaceType == null) + { + return; + } + + // Register an action that accesses the immutable state and reports diagnostics. + compilationContext.RegisterSymbolAction( + symbolContext => { AnalyzeSymbol(symbolContext, interfaceType); }, + SymbolKind.NamedType); + }); + } + + private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol interfaceType) + { + // Check if the symbol implements the interface type + INamedTypeSymbol namedType = (INamedTypeSymbol)context.Symbol; + if (namedType.Interfaces.Contains(interfaceType) && + !namedType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat).Equals(AllowedInternalImplementationTypeName)) + { + Diagnostic diagnostic = Diagnostic.Create(Rule, namedType.Locations[0], namedType.Name, DontInheritInterfaceTypeName); + context.ReportDiagnostic(diagnostic); + } + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Implementation/StatefulAnalyzers/CompilationStartedAnalyzerWithCompilationWideAnalysis.cs b/samples/CSharp/Analyzers/Analyzers.Implementation/StatefulAnalyzers/CompilationStartedAnalyzerWithCompilationWideAnalysis.cs new file mode 100644 index 000000000..68415db1e --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Implementation/StatefulAnalyzers/CompilationStartedAnalyzerWithCompilationWideAnalysis.cs @@ -0,0 +1,166 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Sample.Analyzers +{ + /// + /// Analyzer to demonstrate compilation-wide analysis. + /// + /// Analysis scenario: + /// (a) You have an interface, which is a well-known secure interface, i.e. it is a marker for all secure types in an assembly. + /// (b) You have a method level attribute which marks the owning method as unsecure. An interface which has any member with such an attribute, must be considered unsecure. + /// (c) We want to report diagnostics for types implementing the well-known secure interface that also implement any unsecure interface. + /// + /// Analyzer performs compilation-wide analysis to detect such violating types and reports diagnostics for them in the compilation end action. + /// + /// + /// The analyzer performs this analysis by registering: + /// (a) A compilation start action, which initializes per-compilation state: + /// (i) Immutable state: We fetch and store the type symbols for the well-known secure interface type and unsecure method attribute type in the compilation. + /// (ii) Mutable state: We maintain a set of all types implementing well-known secure interface type and set of all interface types with an unsecure method. + /// (b) A compilation symbol action, which identifies all named types that implement the well-known secure interface, and all method symbols that have the unsecure method attribute. + /// (c) A compilation end action which reports diagnostics for types implementing the well-known secure interface that also implementing any unsecure interface. + /// + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class CompilationStartedAnalyzerWithCompilationWideAnalysis : DiagnosticAnalyzer + { + private const string Title = "Secure types must not implement interfaces with unsecure methods"; + public const string MessageFormat = "Type '{0}' is a secure type as it implements interface '{1}', but it also implements interface '{2}' which has unsecure method(s)."; + private const string Description = "Secure types must not implement interfaces with unsecure methods."; + + internal static DiagnosticDescriptor Rule = + new DiagnosticDescriptor( + DiagnosticIds.CompilationStartedAnalyzerWithCompilationWideAnalysisRuleId, + Title, + MessageFormat, + DiagnosticCategories.Stateful, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: Description); + + public const string UnsecureMethodAttributeName = "MyNamespace.UnsecureMethodAttribute"; + public const string SecureTypeInterfaceName = "MyNamespace.ISecureType"; + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(compilationContext => + { + // Check if the attribute type marking unsecure methods is defined. + INamedTypeSymbol unsecureMethodAttributeType = compilationContext.Compilation.GetTypeByMetadataName(UnsecureMethodAttributeName); + if (unsecureMethodAttributeType == null) + { + return; + } + + // Check if the interface type marking secure types is defined. + INamedTypeSymbol secureTypeInterfaceType = compilationContext.Compilation.GetTypeByMetadataName(SecureTypeInterfaceName); + if (secureTypeInterfaceType == null) + { + return; + } + + // Initialize state in the start action. + CompilationAnalyzer analyzer = new CompilationAnalyzer(unsecureMethodAttributeType, secureTypeInterfaceType); + + // Register an intermediate non-end action that accesses and modifies the state. + compilationContext.RegisterSymbolAction(analyzer.AnalyzeSymbol, SymbolKind.NamedType, SymbolKind.Method); + + // Register an end action to report diagnostics based on the final state. + compilationContext.RegisterCompilationEndAction(analyzer.CompilationEndAction); + }); + } + + private class CompilationAnalyzer + { + private readonly INamedTypeSymbol _unsecureMethodAttributeType; + private readonly INamedTypeSymbol _secureTypeInterfaceType; + + /// + /// List of secure types in the compilation implementing interface . + /// + private readonly List _secureTypes = new List(); + + /// + /// Set of unsecure interface types in the compilation that have methods with an attribute of . + /// + private readonly HashSet _interfacesWithUnsecureMethods = new HashSet(); + + public CompilationAnalyzer(INamedTypeSymbol unsecureMethodAttributeType, INamedTypeSymbol secureTypeInterfaceType) + { + _unsecureMethodAttributeType = unsecureMethodAttributeType; + _secureTypeInterfaceType = secureTypeInterfaceType; + } + + public void AnalyzeSymbol(SymbolAnalysisContext context) + { + switch (context.Symbol.Kind) + { + case SymbolKind.NamedType: + // Check if the symbol implements "_secureTypeInterfaceType". + INamedTypeSymbol namedType = (INamedTypeSymbol)context.Symbol; + if (namedType.AllInterfaces.Contains(_secureTypeInterfaceType)) + { + lock (_secureTypes) + { + _secureTypes.Add(namedType); + } + } + + break; + + case SymbolKind.Method: + // Check if this is an interface method with "_unsecureMethodAttributeType" attribute. + IMethodSymbol method = (IMethodSymbol)context.Symbol; + if (method.ContainingType.TypeKind == TypeKind.Interface && + method.GetAttributes().Any(a => a.AttributeClass.Equals(_unsecureMethodAttributeType))) + { + lock (_interfacesWithUnsecureMethods) + { + _interfacesWithUnsecureMethods.Add(method.ContainingType); + } + } + + break; + } + } + + public void CompilationEndAction(CompilationAnalysisContext context) + { + if (_interfacesWithUnsecureMethods.Count == 0 || _secureTypes.Count == 0) + { + // No violating types. + return; + } + + // Report diagnostic for violating named types. + foreach (INamedTypeSymbol secureType in _secureTypes) + { + foreach (INamedTypeSymbol unsecureInterface in _interfacesWithUnsecureMethods) + { + if (secureType.AllInterfaces.Contains(unsecureInterface)) + { + context.ReportDiagnostic( + Diagnostic.Create( + Rule, + secureType.Locations[0], + secureType.Name, + SecureTypeInterfaceName, + unsecureInterface.Name)); + break; + } + } + } + } + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/CodeBlockAnalyzer.cs b/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/CodeBlockAnalyzer.cs new file mode 100644 index 000000000..04359a217 --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/CodeBlockAnalyzer.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Sample.Analyzers +{ + /// + /// Analyzer for reporting code block diagnostics. + /// It reports diagnostics for all redundant methods which have an empty method body and are not virtual/override. + /// + /// + /// For analyzers that requires analyzing symbols or syntax nodes across a code block, see . + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class CodeBlockAnalyzer : DiagnosticAnalyzer + { + private const string Title = "Remove unnecessary methods"; + public const string MessageFormat = "Method '{0}' is a non-virtual method with an empty body. Consider removing this method from your assembly."; + private const string Description = "Remove unnecessary methods."; + + internal static DiagnosticDescriptor Rule = + new DiagnosticDescriptor( + DiagnosticIds.CodeBlockAnalyzerRuleId, + Title, + MessageFormat, + DiagnosticCategories.Stateless, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: Description); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCodeBlockAction(CodeBlockAction); + } + + private static void CodeBlockAction(CodeBlockAnalysisContext codeBlockContext) + { + // We only care about method bodies. + if (codeBlockContext.OwningSymbol.Kind != SymbolKind.Method) + { + return; + } + + // Report diagnostic for void non-virtual methods with empty method bodies. + IMethodSymbol method = (IMethodSymbol)codeBlockContext.OwningSymbol; + BlockSyntax block = (BlockSyntax)codeBlockContext.CodeBlock.ChildNodes().FirstOrDefault(n => n.Kind() == SyntaxKind.Block); + if (method.ReturnsVoid && !method.IsVirtual && block != null && block.Statements.Count == 0) + { + SyntaxTree tree = block.SyntaxTree; + Location location = method.Locations.First(l => tree.Equals(l.SourceTree)); + Diagnostic diagnostic = Diagnostic.Create(Rule, location, method.Name); + codeBlockContext.ReportDiagnostic(diagnostic); + } + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/CompilationAnalyzer.cs b/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/CompilationAnalyzer.cs new file mode 100644 index 000000000..2cb69e385 --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/CompilationAnalyzer.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Sample.Analyzers +{ + /// + /// Analyzer for reporting compilation diagnostics. + /// It reports diagnostics for analyzer diagnostics that have been suppressed for the entire compilation. + /// + /// + /// For analyzers that requires analyzing symbols or syntax nodes across compilation, see and . + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class CompilationAnalyzer : DiagnosticAnalyzer + { + private const string Title = "Dont suppress analyzer diagnostics"; + public const string MessageFormat = "Analyzer diagnostic '{0}' is suppressed, consider removing this compilation wide suppression."; + private const string Description = "Dont suppress analyzer diagnostics."; + + internal static DiagnosticDescriptor Rule = + new DiagnosticDescriptor( + DiagnosticIds.CompilationAnalyzerRuleId, + Title, + MessageFormat, + DiagnosticCategories.Stateless, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: Description); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationAction(AnalyzeCompilation); + } + + private static void AnalyzeCompilation(CompilationAnalysisContext context) + { + // Get all the suppressed analyzer diagnostic IDs. + IEnumerable suppressedAnalyzerDiagnosticIds = GetSuppressedAnalyzerDiagnosticIds(context.Compilation.Options.SpecificDiagnosticOptions); + + foreach (string suppressedDiagnosticId in suppressedAnalyzerDiagnosticIds) + { + // For all such suppressed diagnostic IDs, produce a diagnostic. + Diagnostic diagnostic = Diagnostic.Create(Rule, Location.None, suppressedDiagnosticId); + context.ReportDiagnostic(diagnostic); + } + } + + private static IEnumerable GetSuppressedAnalyzerDiagnosticIds(ImmutableDictionary specificOptions) + { + foreach (KeyValuePair kvp in specificOptions) + { + if (kvp.Value == ReportDiagnostic.Suppress) + { + if (kvp.Key.StartsWith("CS", StringComparison.OrdinalIgnoreCase) && + int.TryParse(kvp.Key.Substring(2), out int intId)) + { + continue; + } + + if (kvp.Key.StartsWith("BC", StringComparison.OrdinalIgnoreCase) && + int.TryParse(kvp.Key.Substring(2), out intId)) + { + continue; + } + + yield return kvp.Key; + } + } + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/IOperationAnalyzer.cs b/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/IOperationAnalyzer.cs new file mode 100644 index 000000000..5f3d4a30d --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/IOperationAnalyzer.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Sample.Analyzers.StatelessAnalyzers +{ + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public class IOperationAnalyzer : DiagnosticAnalyzer + { + private const string Title = "Reduce allocations and use Array.Empty"; + private const string MessageFormat = "Replace empty array allocation with Array.Empty."; + private const string Description = "Reduce allocations and use Array.Empty."; + + internal static DiagnosticDescriptor Rule = + new DiagnosticDescriptor( + DiagnosticIds.IOperationAnalyzerRuleId, + Title, + MessageFormat, + DiagnosticCategories.Stateless, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: Description); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterOperationAction(AnalyzeOperation, OperationKind.ArrayCreation); + } + + private void AnalyzeOperation(OperationAnalysisContext context) + { + IArrayCreationOperation creationExpression = (IArrayCreationOperation)context.Operation; + + if (creationExpression.DimensionSizes.Length == 1 && creationExpression.DimensionSizes[0].ConstantValue.HasValue) + { + object arrayDimension = creationExpression.DimensionSizes[0].ConstantValue.Value; + if (arrayDimension is int && (int)arrayDimension == 0) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, context.Operation.Syntax.GetLocation())); + } + } + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SemanticModelAnalyzer.cs b/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SemanticModelAnalyzer.cs new file mode 100644 index 000000000..610ff6183 --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SemanticModelAnalyzer.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.IO; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Sample.Analyzers +{ + /// + /// Analyzer for reporting syntax tree diagnostics, that require some semantic analysis. + /// It reports diagnostics for all source files which have at least one declaration diagnostic. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class SemanticModelAnalyzer : DiagnosticAnalyzer + { + private const string Title = "Source file declaration diagnostics count"; + public const string MessageFormat = "Source file '{0}' has '{1}' declaration diagnostic(s)"; + private const string Description = "Source file declaration diagnostic count."; + + internal static DiagnosticDescriptor Rule = + new DiagnosticDescriptor( + DiagnosticIds.SemanticModelAnalyzerRuleId, + Title, + MessageFormat, + DiagnosticCategories.Stateless, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: Description); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterSemanticModelAction(AnalyzeSemanticModel); + } + + private static void AnalyzeSemanticModel(SemanticModelAnalysisContext context) + { + // Find just those source files with declaration diagnostics. + int diagnosticsCount = context.SemanticModel.GetDeclarationDiagnostics().Length; + if (diagnosticsCount > 0) + { + // For all such files, produce a diagnostic. + context.ReportDiagnostic( + Diagnostic.Create( + Rule, + Location.None, + Path.GetFileName(context.SemanticModel.SyntaxTree.FilePath), + diagnosticsCount)); + } + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SymbolAnalyzer.cs b/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SymbolAnalyzer.cs new file mode 100644 index 000000000..a6bea5ef2 --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SymbolAnalyzer.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Sample.Analyzers +{ + /// + /// Analyzer for reporting symbol diagnostics. + /// It reports diagnostics for named type symbols that have members with the same name as the named type. + /// + /// + /// For analyzers that requires analyzing symbols or syntax nodes across compilation, see and . + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class SymbolAnalyzer : DiagnosticAnalyzer + { + private const string Title = "Do not declare members with same name as containing type"; + public const string MessageFormat = "Type '{0}' has one or more members with the same name, considering renaming the type or the members."; + private const string Description = "Do not declare members with same name as containing type."; + + internal static DiagnosticDescriptor Rule = + new DiagnosticDescriptor( + DiagnosticIds.SymbolAnalyzerRuleId, + Title, + MessageFormat, + DiagnosticCategories.Stateless, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: Description); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType); + } + + private static void AnalyzeSymbol(SymbolAnalysisContext context) + { + INamedTypeSymbol namedTypeSymbol = (INamedTypeSymbol)context.Symbol; + + // Find just those named type symbols that have members with the same name as the named type. + if (namedTypeSymbol.GetMembers(namedTypeSymbol.Name).Any()) + { + // For all such symbols, report a diagnostic. + context.ReportDiagnostic( + Diagnostic.Create( + Rule, + namedTypeSymbol.Locations[0], + namedTypeSymbol.Name)); + } + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SyntaxNodeAnalyzer.cs b/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SyntaxNodeAnalyzer.cs new file mode 100644 index 000000000..0221a1dab --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SyntaxNodeAnalyzer.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Sample.Analyzers +{ + /// + /// Analyzer for reporting syntax node diagnostics. + /// It reports diagnostics for implicitly typed local variables, recommending explicit type specification. + /// + /// + /// For analyzers that requires analyzing symbols or syntax nodes across compilation, see and . + /// For analyzers that requires analyzing symbols or syntax nodes across a code block, see . + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class SyntaxNodeAnalyzer : DiagnosticAnalyzer + { + private const string Title = "Declare explicit type for local declarations."; + public const string MessageFormat = "Local '{0}' is implicitly typed. Consider specifying its type explicitly in the declaration."; + private const string Description = "Declare explicit type for local declarations."; + + internal static DiagnosticDescriptor Rule = + new DiagnosticDescriptor( + DiagnosticIds.SyntaxNodeAnalyzerRuleId, + Title, + MessageFormat, + DiagnosticCategories.Stateless, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: Description); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.VariableDeclaration); + } + + private static void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) + { + // Find implicitly typed variable declarations. + VariableDeclarationSyntax declaration = (VariableDeclarationSyntax)context.Node; + if (declaration.Type.IsVar) + { + foreach (VariableDeclaratorSyntax variable in declaration.Variables) + { + // For all such locals, report a diagnostic. + context.ReportDiagnostic( + Diagnostic.Create( + Rule, + variable.GetLocation(), + variable.Identifier.ValueText)); + } + } + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SyntaxTreeAnalyzer.cs b/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SyntaxTreeAnalyzer.cs new file mode 100644 index 000000000..7525db701 --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SyntaxTreeAnalyzer.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.IO; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Sample.Analyzers +{ + /// + /// Analyzer for reporting syntax tree diagnostics. + /// It reports diagnostics for all source files which have documentation comment diagnostics turned off. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class SyntaxTreeAnalyzer : DiagnosticAnalyzer + { + private const string Title = "Do not suppress documentation comment diagnostics"; + public const string MessageFormat = "Enable documentation comment diagnostics on source file '{0}'."; + private const string Description = "Do not suppress documentation comment diagnostics."; + + internal static DiagnosticDescriptor Rule = + new DiagnosticDescriptor( + DiagnosticIds.SyntaxTreeAnalyzerRuleId, + Title, + MessageFormat, + DiagnosticCategories.Stateless, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: Description); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterSyntaxTreeAction(AnalyzeSyntaxTree); + } + + private static void AnalyzeSyntaxTree(SyntaxTreeAnalysisContext context) + { + // Find source files with documentation comment diagnostics turned off. + if (context.Tree.Options.DocumentationMode != DocumentationMode.Diagnose) + { + // For all such files, produce a diagnostic. + context.ReportDiagnostic( + Diagnostic.Create( + Rule, + Location.None, + Path.GetFileName(context.Tree.FilePath))); + } + } + } +} diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/install.ps1 b/samples/CSharp/Analyzers/Analyzers.Implementation/tools/install.ps1 similarity index 100% rename from src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/install.ps1 rename to samples/CSharp/Analyzers/Analyzers.Implementation/tools/install.ps1 diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/uninstall.ps1 b/samples/CSharp/Analyzers/Analyzers.Implementation/tools/uninstall.ps1 similarity index 100% rename from src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/uninstall.ps1 rename to samples/CSharp/Analyzers/Analyzers.Implementation/tools/uninstall.ps1 diff --git a/samples/CSharp/Analyzers/Analyzers.Test/Analyzers.CSharp.UnitTests.csproj b/samples/CSharp/Analyzers/Analyzers.Test/Analyzers.CSharp.UnitTests.csproj new file mode 100644 index 000000000..db2db337f --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Test/Analyzers.CSharp.UnitTests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.0 + + true + true + + + + + + + + + + + + + diff --git a/samples/CSharp/Analyzers/Analyzers.Test/Tests/CodeBlockAnalyzerUnitTests.cs b/samples/CSharp/Analyzers/Analyzers.Test/Tests/CodeBlockAnalyzerUnitTests.cs new file mode 100644 index 000000000..105d51a92 --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Test/Tests/CodeBlockAnalyzerUnitTests.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using Verify = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier; + +namespace Sample.Analyzers.Test +{ + public class CodeBlockAnalyzerUnitTests + { + [Fact] + public async Task CodeBlockAnalyzerTest() + { + string test = @" +class C +{ + public void M1() + { + } + + public virtual void M2() + { + } + + public int M3() + { + } +}"; + DiagnosticResult[] expected = + { + Verify.Diagnostic().WithLocation(4, 17).WithArguments("M1"), + DiagnosticResult.CompilerError("CS0161").WithLocation(12, 16).WithMessage("'C.M3()': not all code paths return a value"), + }; + await Verify.VerifyAnalyzerAsync(test, expected); + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Test/Tests/CodeBlockStartedAnalyzerUnitTests.cs b/samples/CSharp/Analyzers/Analyzers.Test/Tests/CodeBlockStartedAnalyzerUnitTests.cs new file mode 100644 index 000000000..2ed9aba9f --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Test/Tests/CodeBlockStartedAnalyzerUnitTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using Verify = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier; + +namespace Sample.Analyzers.Test +{ + public class CodeBlockStartedAnalyzerUnitTests + { + [Fact] + public async Task CodeBlockStartedAnalyzerTest() + { + string test = @" +class C +{ + public int M1(int p1, int p2) + { + return M2(p1, p1); + } + + public int M2(int p1, int p2) + { + return p1 + p2; + } +}"; + DiagnosticResult expected = Verify.Diagnostic().WithArguments("p2", "M1").WithLocation(4, 31); + await Verify.VerifyAnalyzerAsync(test, expected); + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Test/Tests/CompilationAnalyzerUnitTests.cs b/samples/CSharp/Analyzers/Analyzers.Test/Tests/CompilationAnalyzerUnitTests.cs new file mode 100644 index 000000000..88fa7ec04 --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Test/Tests/CompilationAnalyzerUnitTests.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Xunit; +using Verify = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier; + +namespace Sample.Analyzers.Test +{ + public class CompilationAnalyzerUnitTests + { + [Fact] + public async Task CompilationAnalyzerTest() + { + string test = @" +class C +{ + public void M() + { + } +}"; + + KeyValuePair specificOption = + new KeyValuePair(DiagnosticIds.SymbolAnalyzerRuleId, ReportDiagnostic.Error); + + await new CSharpAnalyzerTest + { + TestCode = test, + ExpectedDiagnostics = + { + DiagnosticResult.CompilerError("CS5001").WithMessage("Program does not contain a static 'Main' method suitable for an entry point"), + }, + SolutionTransforms = + { + (solution, projectId) => + { + CSharpCompilationOptions options = (CSharpCompilationOptions)solution.GetProject(projectId).CompilationOptions + .WithOutputKind(OutputKind.ConsoleApplication) + .WithSpecificDiagnosticOptions(new[] { specificOption }); + return solution.WithProjectCompilationOptions(projectId, options); + }, + } + }.RunAsync(); + + specificOption = new KeyValuePair(DiagnosticIds.SymbolAnalyzerRuleId, ReportDiagnostic.Suppress); + await new CSharpAnalyzerTest + { + TestCode = test, + ExpectedDiagnostics = + { + DiagnosticResult.CompilerError("CS5001").WithMessage("Program does not contain a static 'Main' method suitable for an entry point"), + Verify.Diagnostic().WithArguments(DiagnosticIds.SymbolAnalyzerRuleId), + }, + SolutionTransforms = + { + (solution, projectId) => + { + CSharpCompilationOptions options = (CSharpCompilationOptions)solution.GetProject(projectId).CompilationOptions + .WithOutputKind(OutputKind.ConsoleApplication) + .WithSpecificDiagnosticOptions(new[] { specificOption }); + return solution.WithProjectCompilationOptions(projectId, options); + }, + } + }.RunAsync(); + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Test/Tests/CompilationStartedAnalyzerUnitTests.cs b/samples/CSharp/Analyzers/Analyzers.Test/Tests/CompilationStartedAnalyzerUnitTests.cs new file mode 100644 index 000000000..93ee3c79e --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Test/Tests/CompilationStartedAnalyzerUnitTests.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using Verify = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier; + +namespace Sample.Analyzers.Test +{ + public class CompilationStartedAnalyzerUnitTests + { + [Fact] + public async Task CompilationStartedAnalyzerTest() + { + string test = @" +namespace MyInterfaces +{ + public interface Interface {} + class MyInterfaceImpl : Interface + { + } + class MyInterfaceImpl2 : Interface + { + } +}"; + DiagnosticResult expected = Verify.Diagnostic().WithArguments("MyInterfaceImpl2", CompilationStartedAnalyzer.DontInheritInterfaceTypeName).WithLocation(8, 11); + await Verify.VerifyAnalyzerAsync(test, expected); + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Test/Tests/CompilationStartedAnalyzerWithCompilationWideAnalysisUnitTests.cs b/samples/CSharp/Analyzers/Analyzers.Test/Tests/CompilationStartedAnalyzerWithCompilationWideAnalysisUnitTests.cs new file mode 100644 index 000000000..f89860879 --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Test/Tests/CompilationStartedAnalyzerWithCompilationWideAnalysisUnitTests.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using Verify = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier; + +namespace Sample.Analyzers.Test +{ + public class CompilationStartedAnalyzerWithCompilationWideAnalysisUnitTests + { + [Fact] + public async Task CompilationStartedAnalyzerWithCompilationWideAnalysisTest() + { + string test = @" +namespace MyNamespace +{ + public class UnsecureMethodAttribute : System.Attribute { } + + public interface ISecureType { } + + public interface IUnsecureInterface + { + [UnsecureMethodAttribute] + void F(); + } + + class MyInterfaceImpl1 : IUnsecureInterface + { + public void F() {} + } + + class MyInterfaceImpl2 : IUnsecureInterface, ISecureType + { + public void F() {} + } + + class MyInterfaceImpl3 : ISecureType + { + public void F() {} + } +}"; + DiagnosticResult expected = Verify.Diagnostic().WithArguments("MyInterfaceImpl2", CompilationStartedAnalyzerWithCompilationWideAnalysis.SecureTypeInterfaceName, "IUnsecureInterface").WithLocation(19, 11); + await Verify.VerifyAnalyzerAsync(test, expected); + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Test/Tests/SemanticModelAnalyzerUnitTests.cs b/samples/CSharp/Analyzers/Analyzers.Test/Tests/SemanticModelAnalyzerUnitTests.cs new file mode 100644 index 000000000..24b2c9a41 --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Test/Tests/SemanticModelAnalyzerUnitTests.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using Verify = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier; + +namespace Sample.Analyzers.Test +{ + public class SemanticModelAnalyzerUnitTests + { + [Fact] + public async Task SemanticModelAnalyzerTest() + { + string test = @" +class C +{ + public async int M() + { + } +}"; + DiagnosticResult[] expected = + { + Verify.Diagnostic().WithArguments("Test0.cs", 1), + DiagnosticResult.CompilerError("CS0161").WithLocation(4, 22).WithMessage("'C.M()': not all code paths return a value"), + DiagnosticResult.CompilerError("CS1983").WithLocation(4, 22).WithMessage("The return type of an async method must be void, Task or Task"), + }; + await Verify.VerifyAnalyzerAsync(test, expected); + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Test/Tests/SymbolAnalyzerUnitTests.cs b/samples/CSharp/Analyzers/Analyzers.Test/Tests/SymbolAnalyzerUnitTests.cs new file mode 100644 index 000000000..47941a358 --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Test/Tests/SymbolAnalyzerUnitTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using Verify = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier; + +namespace Sample.Analyzers.Test +{ + public class SymbolAnalyzerUnitTests + { + [Fact] + public async Task SymbolAnalyzerTest() + { + string test = @" +class BadOne +{ + public void BadOne() {} +} + +class GoodOne +{ +}"; + DiagnosticResult[] expected = + { + Verify.Diagnostic().WithLocation(2, 7).WithArguments("BadOne"), + DiagnosticResult.CompilerError("CS0542").WithLocation(4, 17).WithMessage("'BadOne': member names cannot be the same as their enclosing type"), + }; + await Verify.VerifyAnalyzerAsync(test, expected); + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Test/Tests/SyntaxNodeAnalyzerUnitTests.cs b/samples/CSharp/Analyzers/Analyzers.Test/Tests/SyntaxNodeAnalyzerUnitTests.cs new file mode 100644 index 000000000..c0f7a5624 --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Test/Tests/SyntaxNodeAnalyzerUnitTests.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using Verify = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier; + +namespace Sample.Analyzers.Test +{ + public class SyntaxNodeAnalyzerUnitTests + { + [Fact] + public async Task SyntaxNodeAnalyzerTest() + { + string test = @" +class C +{ + public void M() + { + var implicitTypedLocal = 0; + int explicitTypedLocal = 1; + } +}"; + DiagnosticResult expected = Verify.Diagnostic().WithArguments("implicitTypedLocal").WithLocation(6, 13); + await Verify.VerifyAnalyzerAsync(test, expected); + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Test/Tests/SyntaxTreeAnalyzerUnitTests.cs b/samples/CSharp/Analyzers/Analyzers.Test/Tests/SyntaxTreeAnalyzerUnitTests.cs new file mode 100644 index 000000000..b79379b6f --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Test/Tests/SyntaxTreeAnalyzerUnitTests.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Xunit; +using Verify = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier; + +namespace Sample.Analyzers.Test +{ + public class SyntaxTreeAnalyzerUnitTests + { + [Fact] + public async Task SyntaxTreeAnalyzerTest() + { + string test = @" +class C +{ + public void M() + { + } +}"; + DiagnosticResult expected = Verify.Diagnostic().WithArguments("Test0.cs"); + + await new CSharpAnalyzerTest + { + TestCode = test, + SolutionTransforms = + { + (solution, projectId) => + { + CSharpParseOptions parseOptions = CSharpParseOptions.Default.WithDocumentationMode(DocumentationMode.Diagnose); + return solution.WithProjectParseOptions(projectId, parseOptions); + }, + } + }.RunAsync(); + + await new CSharpAnalyzerTest + { + TestCode = test, + ExpectedDiagnostics = { expected }, + SolutionTransforms = + { + (solution, projectId) => + { + CSharpParseOptions parseOptions = CSharpParseOptions.Default.WithDocumentationMode(DocumentationMode.None); + return solution.WithProjectParseOptions(projectId, parseOptions); + }, + } + }.RunAsync(); + + await new CSharpAnalyzerTest + { + TestCode = test, + ExpectedDiagnostics = { expected }, + SolutionTransforms = + { + (solution, projectId) => + { + CSharpParseOptions parseOptions = CSharpParseOptions.Default.WithDocumentationMode(DocumentationMode.Parse); + return solution.WithProjectParseOptions(projectId, parseOptions); + }, + } + }.RunAsync(); + } + } +} diff --git a/samples/CSharp/Analyzers/Analyzers.Vsix/Analyzers.CSharp.Vsix.csproj b/samples/CSharp/Analyzers/Analyzers.Vsix/Analyzers.CSharp.Vsix.csproj new file mode 100644 index 000000000..e8b263807 --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Vsix/Analyzers.CSharp.Vsix.csproj @@ -0,0 +1,19 @@ + + + + net472 + Analyzers.CSharp.Vsix + Analyzers.CSharp + false + false + false + false + false + + + + + + + + \ No newline at end of file diff --git a/samples/CSharp/Analyzers/Analyzers.Vsix/source.extension.vsixmanifest b/samples/CSharp/Analyzers/Analyzers.Vsix/source.extension.vsixmanifest new file mode 100644 index 000000000..8bd80bb12 --- /dev/null +++ b/samples/CSharp/Analyzers/Analyzers.Vsix/source.extension.vsixmanifest @@ -0,0 +1,22 @@ + + + + + Analyzers + This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn"). + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/CSharpToVisualBasicConverter.csproj b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/CSharpToVisualBasicConverter.csproj new file mode 100644 index 000000000..5aec396c4 --- /dev/null +++ b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/CSharpToVisualBasicConverter.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + + + + + + + + diff --git a/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Cleanup/CurlyCleanup.cs b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Cleanup/CurlyCleanup.cs new file mode 100644 index 000000000..2fa4adc40 --- /dev/null +++ b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Cleanup/CurlyCleanup.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace CSharpToVisualBasicConverter.Cleanup +{ + internal class CurlyCleanup : CSharpSyntaxRewriter + { + private readonly SyntaxTree syntaxTree; + + public CurlyCleanup(SyntaxTree syntaxTree) + { + this.syntaxTree = syntaxTree; + } + + public override SyntaxToken VisitToken(SyntaxToken token) + { + token = base.VisitToken(token); + if (token.IsMissing) + { + return token; + } + + if (!token.IsKind(SyntaxKind.CloseBraceToken)) + { + return token; + } + + SyntaxToken nextToken = token.GetNextToken(includeSkipped: true); + + int tokenLine = syntaxTree.GetText().Lines.IndexOf(token.Span.Start); + int nextTokenLine = syntaxTree.GetText().Lines.IndexOf(nextToken.Span.Start); + bool nextTokenIsCloseBrace = nextToken.IsKind(SyntaxKind.CloseBraceToken); + + int expectedDiff = nextTokenIsCloseBrace ? 1 : 2; + if (nextTokenLine == tokenLine + expectedDiff) + { + return token; + } + + System.Collections.Generic.IEnumerable nonNewLineTrivia = token.TrailingTrivia.Where(t => !t.IsKind(SyntaxKind.EndOfLineTrivia)); + System.Collections.Generic.IEnumerable newTrivia = nonNewLineTrivia.Concat(Enumerable.Repeat(SyntaxFactory.EndOfLine("\r\n"), expectedDiff)); + + return token.WithTrailingTrivia(SyntaxFactory.TriviaList(newTrivia)); + } + } +} diff --git a/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Cleanup/MissingCurlyCleanup.cs b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Cleanup/MissingCurlyCleanup.cs new file mode 100644 index 000000000..d392ed8c7 --- /dev/null +++ b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Cleanup/MissingCurlyCleanup.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CSharpToVisualBasicConverter.Cleanup +{ + internal class MissingCurlyCleanup : CSharpSyntaxRewriter + { + private readonly SyntaxTree syntaxTree; + + public MissingCurlyCleanup(SyntaxTree syntaxTree) + { + this.syntaxTree = syntaxTree; + } + + public override SyntaxNode VisitIfStatement(IfStatementSyntax node) + { + node = (IfStatementSyntax)base.VisitIfStatement(node); + if (node.Statement.IsKind(SyntaxKind.Block)) + { + return node; + } + + BlockSyntax block = SyntaxFactory.Block(statements: SyntaxFactory.SingletonList(node.Statement)); + return SyntaxFactory.IfStatement( + node.IfKeyword, + node.OpenParenToken, + node.Condition, + node.CloseParenToken, + block, + node.Else); + } + + public override SyntaxNode VisitElseClause(ElseClauseSyntax node) + { + node = (ElseClauseSyntax)base.VisitElseClause(node); + if (node.Statement.IsKind(SyntaxKind.Block) || node.Statement.IsKind(SyntaxKind.IfStatement)) + { + return node; + } + + BlockSyntax block = SyntaxFactory.Block(statements: SyntaxFactory.SingletonList(node.Statement)); + return SyntaxFactory.ElseClause( + node.ElseKeyword, + block); + } + } +} diff --git a/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Cleanup/NewLineCleanup.cs b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Cleanup/NewLineCleanup.cs new file mode 100644 index 000000000..8ac05a169 --- /dev/null +++ b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Cleanup/NewLineCleanup.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace CSharpToVisualBasicConverter.Cleanup +{ + internal class NewLineCleanup : CSharpSyntaxRewriter + { + private readonly SyntaxTree syntaxTree; + + public NewLineCleanup(SyntaxTree syntaxTree) + { + this.syntaxTree = syntaxTree; + } + + public override SyntaxToken VisitToken(SyntaxToken token) + { + token = base.VisitToken(token); + if (token.IsMissing) + { + return token; + } + + bool changed; + + do + { + changed = false; + if ((token.HasLeadingTrivia && token.LeadingTrivia.Count >= 2) || + (token.HasTrailingTrivia && token.TrailingTrivia.Count >= 2)) + { + List newLeadingTrivia = RemoveBlankLines(token.LeadingTrivia, ref changed); + List newTrailingTrivia = RemoveBlankLines(token.TrailingTrivia, ref changed); + + if (changed) + { + token = token.WithLeadingTrivia(SyntaxFactory.TriviaList(newLeadingTrivia)); + token = token.WithTrailingTrivia(SyntaxFactory.TriviaList(newTrailingTrivia)); + } + } + } + while (changed); + + return token; + } + + private static List RemoveBlankLines(SyntaxTriviaList trivia, ref bool changed) + { + List newTrivia = new List(); + + for (int i = 0; i < trivia.Count;) + { + SyntaxTrivia trivia1 = trivia.ElementAt(i); + newTrivia.Add(trivia1); + + if (i < trivia.Count - 1) + { + SyntaxTrivia trivia2 = trivia.ElementAt(i + 1); + + if (trivia1.IsKind(SyntaxKind.EndOfLineTrivia) && + trivia2.IsKind(SyntaxKind.EndOfLineTrivia)) + { + changed = true; + i += 2; + continue; + } + } + + i++; + } + + return newTrivia; + } + } +} diff --git a/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Cleanup/WhiteSpaceCleanup.cs b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Cleanup/WhiteSpaceCleanup.cs new file mode 100644 index 000000000..ad93fb75f --- /dev/null +++ b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Cleanup/WhiteSpaceCleanup.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace CSharpToVisualBasicConverter.Cleanup +{ + internal class WhiteSpaceCleanup : CSharpSyntaxRewriter + { + private readonly SyntaxTree syntaxTree; + + public WhiteSpaceCleanup(SyntaxTree syntaxTree) + { + this.syntaxTree = syntaxTree; + } + + public override SyntaxToken VisitToken(SyntaxToken token) + { + token = base.VisitToken(token); + if (token.IsMissing) + { + return token; + } + + bool changed; + + do + { + changed = false; + if ((token.HasTrailingTrivia && token.TrailingTrivia.Count >= 3) || + (token.HasLeadingTrivia && token.LeadingTrivia.Count >= 3)) + { + List newLeadingTrivia = RemoveBlankLineTrivia(token.LeadingTrivia, ref changed); + List newTrailingTrivia = RemoveBlankLineTrivia(token.TrailingTrivia, ref changed); + + if (changed) + { + token = token.WithLeadingTrivia(SyntaxFactory.TriviaList(newLeadingTrivia)); + token = token.WithTrailingTrivia(SyntaxFactory.TriviaList(newTrailingTrivia)); + } + } + } + while (changed); + + return token; + } + + private static List RemoveBlankLineTrivia(SyntaxTriviaList trivia, ref bool changed) + { + List newTrivia = new List(); + + for (int i = 0; i < trivia.Count;) + { + SyntaxTrivia trivia1 = trivia.ElementAt(i); + newTrivia.Add(trivia1); + + if (i < trivia.Count - 2) + { + SyntaxTrivia trivia2 = trivia.ElementAt(i + 1); + SyntaxTrivia trivia3 = trivia.ElementAt(i + 2); + + if (trivia1.IsKind(SyntaxKind.EndOfLineTrivia) && + trivia2.IsKind(SyntaxKind.WhitespaceTrivia) && + trivia3.IsKind(SyntaxKind.EndOfLineTrivia)) + { + // Skip the whitespace with a newline. + newTrivia.Add(trivia3); + changed = true; + i += 3; + continue; + } + } + + i++; + } + + return newTrivia; + } + } +} diff --git a/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Converting/Converter.NodeVisitor.cs b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Converting/Converter.NodeVisitor.cs new file mode 100644 index 000000000..d164cfe79 --- /dev/null +++ b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Converting/Converter.NodeVisitor.cs @@ -0,0 +1,2156 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using CSharpToVisualBasicConverter.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using CS = Microsoft.CodeAnalysis.CSharp; +using VB = Microsoft.CodeAnalysis.VisualBasic; + +namespace CSharpToVisualBasicConverter +{ + public partial class Converter + { + private class NodeVisitor : CS.CSharpSyntaxVisitor + { + private readonly SourceText text; + private readonly IDictionary identifierMap; + private readonly bool convertStrings; + private readonly StatementVisitor statementVisitor; + + public NodeVisitor(SourceText text, IDictionary identifierMap, bool convertStrings) + { + this.text = text; + this.identifierMap = identifierMap; + this.convertStrings = convertStrings; + statementVisitor = new StatementVisitor(this, text); + } + + internal SyntaxToken VisitToken(SyntaxToken token) + { + SyntaxToken result = VisitTokenWorker(token); + return CopyTriviaTo(token, result); + } + + private SyntaxToken CopyTriviaTo(SyntaxToken from, SyntaxToken to) + { + if (from.HasLeadingTrivia) + { + to = to.WithLeadingTrivia(ConvertTrivia(from.LeadingTrivia)); + } + + if (from.HasTrailingTrivia) + { + to = to.WithTrailingTrivia(ConvertTrivia(from.TrailingTrivia)); + } + + return to; + } + + private SyntaxToken VisitTokenWorker(SyntaxToken token) + { + SyntaxKind kind = token.Kind(); + if (kind == CS.SyntaxKind.IdentifierToken) + { + return VB.SyntaxFactory.Identifier(token.ValueText); + } + + switch (kind) + { + case CS.SyntaxKind.AbstractKeyword: + return token.Parent is CS.Syntax.TypeDeclarationSyntax + ? VB.SyntaxFactory.Token(VB.SyntaxKind.MustInheritKeyword) + : VB.SyntaxFactory.Token(VB.SyntaxKind.MustOverrideKeyword); + + case CS.SyntaxKind.AssemblyKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.AssemblyKeyword); + case CS.SyntaxKind.AsyncKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.AsyncKeyword); + case CS.SyntaxKind.BoolKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.BooleanKeyword); + case CS.SyntaxKind.ByteKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.ByteKeyword); + case CS.SyntaxKind.ConstKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.ConstKeyword); + case CS.SyntaxKind.IfKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.IfKeyword); + case CS.SyntaxKind.IntKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.IntegerKeyword); + case CS.SyntaxKind.InternalKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.FriendKeyword); + case CS.SyntaxKind.ModuleKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.ModuleKeyword); + case CS.SyntaxKind.NewKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.OverloadsKeyword); + case CS.SyntaxKind.OutKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.ByRefKeyword); + case CS.SyntaxKind.OverrideKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.OverridesKeyword); + case CS.SyntaxKind.ParamsKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.ParamArrayKeyword); + case CS.SyntaxKind.PartialKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.PartialKeyword); + case CS.SyntaxKind.PrivateKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.PrivateKeyword); + case CS.SyntaxKind.ProtectedKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.ProtectedKeyword); + case CS.SyntaxKind.PublicKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.PublicKeyword); + case CS.SyntaxKind.ReadOnlyKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.ReadOnlyKeyword); + case CS.SyntaxKind.RefKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.ByRefKeyword); + case CS.SyntaxKind.SealedKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.NotOverridableKeyword); + case CS.SyntaxKind.ShortKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.ShortKeyword); + case CS.SyntaxKind.StaticKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.SharedKeyword); + case CS.SyntaxKind.ThisKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.MeKeyword); + case CS.SyntaxKind.UIntKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.UIntegerKeyword); + case CS.SyntaxKind.UsingKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.ImportsKeyword); + case CS.SyntaxKind.VirtualKeyword: + return VB.SyntaxFactory.Token(VB.SyntaxKind.OverridableKeyword); + case CS.SyntaxKind.NumericLiteralToken: + return VB.SyntaxFactory.IntegerLiteralToken(token.ValueText, VB.Syntax.LiteralBase.Decimal, VB.Syntax.TypeCharacter.None, 0); + case CS.SyntaxKind.CharacterLiteralToken: + { + string text = Microsoft.CodeAnalysis.VisualBasic.SymbolDisplay.FormatPrimitive(token.ValueText[0], quoteStrings: true, useHexadecimalNumbers: true); + return VB.SyntaxFactory.CharacterLiteralToken(text, token.ValueText[0]); + } + + case CS.SyntaxKind.StringLiteralToken: + { + string text = Microsoft.CodeAnalysis.VisualBasic.SymbolDisplay.FormatPrimitive(token.ValueText, quoteStrings: true, useHexadecimalNumbers: true); + return VB.SyntaxFactory.StringLiteralToken(text, token.ValueText); + } + } + + if (CS.SyntaxFacts.IsKeywordKind(kind) || + kind == CS.SyntaxKind.None) + { + return VB.SyntaxFactory.Identifier(token.ValueText); + } + else if (CS.SyntaxFacts.IsPunctuation(kind)) + { + return VB.SyntaxFactory.Token(VB.SyntaxKind.EmptyToken); + } + else + { + throw new NotImplementedException(); + } + } + + internal TSyntaxNode Visit(SyntaxNode node) where TSyntaxNode : SyntaxNode + { + return (TSyntaxNode)Visit(node); + } + + private VB.Syntax.TypeSyntax VisitType(CS.Syntax.TypeSyntax type) + { + return Visit(type); + } + + internal VB.Syntax.NameSyntax VisitName(CS.Syntax.NameSyntax name) + { + return Visit(name); + } + + internal VB.Syntax.ExpressionSyntax VisitExpression(CS.Syntax.ExpressionSyntax expression) + { + return ConvertToExpression(Visit(expression)); + } + + internal VB.Syntax.StatementSyntax VisitStatement(CS.Syntax.ExpressionSyntax expression) + { + return ConvertToStatement(Visit(expression)); + } + + private static VB.Syntax.ExpressionSyntax ConvertToExpression(SyntaxNode node) + { + if (node == null) + { + return null; + } + else if (node is VB.Syntax.ExpressionSyntax) + { + return (VB.Syntax.ExpressionSyntax)node; + } + + string error = CreateCouldNotBeConvertedString(((SyntaxNode)node).ToFullString(), typeof(VB.Syntax.ExpressionSyntax)); + return VB.SyntaxFactory.StringLiteralExpression(VB.SyntaxFactory.StringLiteralToken(error, error)); + } + + private VB.Syntax.StatementSyntax ConvertToStatement(SyntaxNode node) + { + if (node == null) + { + return null; + } + else if (node is VB.Syntax.StatementSyntax) + { + return (VB.Syntax.StatementSyntax)node; + } + else if (node is VB.Syntax.InvocationExpressionSyntax) + { + return VB.SyntaxFactory.ExpressionStatement((VB.Syntax.InvocationExpressionSyntax)node); + } + else if (node is VB.Syntax.AwaitExpressionSyntax) + { + return VB.SyntaxFactory.ExpressionStatement((VB.Syntax.AwaitExpressionSyntax)node); + } + else + { + // can happen in error scenarios + return CreateBadStatement(((SyntaxNode)node).ToFullString(), typeof(VB.Syntax.StatementSyntax)); + } + } + + private SyntaxTriviaList ConvertTrivia(SyntaxTriviaList list) + { + return VB.SyntaxFactory.TriviaList(list.Where(t => !CS.SyntaxFacts.IsDocumentationCommentTrivia(t.Kind())) + .SelectMany(VisitTrivia).Aggregate(new List(), + (builder, trivia) => { builder.Add(trivia); return builder; })); + } + + public override SyntaxNode VisitCompilationUnit(CS.Syntax.CompilationUnitSyntax node) + { + SyntaxList blocks = List(node.AttributeLists.Select(Visit)); + + SyntaxList attributes = blocks.Count > 0 + ? List(VB.SyntaxFactory.AttributesStatement(blocks)) + : default(SyntaxList); + + IEnumerable vbImports = node.Externs.Select(Visit) + .Concat(node.Usings.Select(Visit)); + + return VB.SyntaxFactory.CompilationUnit( + List(), + List(vbImports), + attributes, + List(node.Members.Select(Visit)), + VB.SyntaxFactory.Token(VB.SyntaxKind.EndOfFileToken)); + } + + private SyntaxList ConvertAttributes( + IEnumerable list) + { + return List(list.Select(Visit)); + } + + public override SyntaxNode VisitUsingDirective(CS.Syntax.UsingDirectiveSyntax directive) + { + if (directive.Alias == null) + { + return VB.SyntaxFactory.ImportsStatement( + CopyTriviaTo(directive.UsingKeyword, VB.SyntaxFactory.Token(VB.SyntaxKind.ImportsKeyword)), + SeparatedList(VB.SyntaxFactory.SimpleImportsClause(VisitName(directive.Name)))); + } + else + { + return VB.SyntaxFactory.ImportsStatement( + CopyTriviaTo(directive.UsingKeyword, VB.SyntaxFactory.Token(VB.SyntaxKind.ImportsKeyword)), + SeparatedList( + VB.SyntaxFactory.SimpleImportsClause(VB.SyntaxFactory.ImportAliasClause(ConvertIdentifier(directive.Alias.Name)), VisitName(directive.Name)))); + } + } + + public override SyntaxNode VisitIdentifierName(CS.Syntax.IdentifierNameSyntax node) + { + return VB.SyntaxFactory.IdentifierName(ConvertIdentifier(node)); + } + + internal SyntaxToken ConvertIdentifier(CS.Syntax.IdentifierNameSyntax name) + { + return ConvertIdentifier(name.Identifier); + } + + internal SyntaxToken ConvertIdentifier(SyntaxToken name) + { + string text = name.ValueText; + if (identifierMap != null && identifierMap.TryGetValue(text, out string replace)) + { + text = replace; + } + + SyntaxNode node = name.Parent; + bool afterDot1 = node.IsParentKind(CS.SyntaxKind.QualifiedName) && ((CS.Syntax.QualifiedNameSyntax)node.Parent).Right == node; + bool afterDot2 = node.IsParentKind(CS.SyntaxKind.SimpleMemberAccessExpression) && ((CS.Syntax.MemberAccessExpressionSyntax)node.Parent).Name == node; + bool afterDot = afterDot1 || afterDot2; + + if (!afterDot && VB.SyntaxFacts.GetKeywordKind(text) != VB.SyntaxKind.None) + { + return VB.SyntaxFactory.Identifier( + "[" + text + "]", + isBracketed: true, + identifierText: text, + typeCharacter: VB.Syntax.TypeCharacter.None); + } + + return VB.SyntaxFactory.Identifier(text); + } + + internal IEnumerable VisitTrivia(SyntaxTrivia trivia) + { + if (trivia.HasStructure) + { + VB.Syntax.StructuredTriviaSyntax structure = Visit(trivia.GetStructure()); + + if (structure is VB.Syntax.BadDirectiveTriviaSyntax) + { + yield return VB.SyntaxFactory.CommentTrivia(structure.ToFullString()); + } + else + { + yield return VB.SyntaxFactory.Trivia(structure); + } + } + else + { + switch (trivia.Kind()) + { + case CS.SyntaxKind.MultiLineCommentTrivia: + case CS.SyntaxKind.SingleLineCommentTrivia: + yield return VB.SyntaxFactory.CommentTrivia("'" + trivia.ToString().Substring(2)); + break; + case CS.SyntaxKind.EndOfLineTrivia: + case CS.SyntaxKind.WhitespaceTrivia: + yield break; + case CS.SyntaxKind.DisabledTextTrivia: + yield return VB.SyntaxFactory.DisabledTextTrivia(trivia.ToString()); + break; + default: + throw new NotImplementedException(); + } + } + } + + public override SyntaxNode VisitAliasQualifiedName(CS.Syntax.AliasQualifiedNameSyntax node) + { + // TODO: don't throw away the alias + return Visit(node.Name); + } + + public override SyntaxNode VisitQualifiedName(CS.Syntax.QualifiedNameSyntax node) + { + if (node.Right.IsKind(CS.SyntaxKind.GenericName)) + { + GenericNameSyntax genericName = (CS.Syntax.GenericNameSyntax)node.Right; + return VB.SyntaxFactory.QualifiedName( + VisitName(node.Left), + VB.SyntaxFactory.GenericName( + ConvertIdentifier(genericName.Identifier), + ConvertTypeArguments(genericName.TypeArgumentList))); + } + else if (node.Right.IsKind(CS.SyntaxKind.IdentifierName)) + { + return VB.SyntaxFactory.QualifiedName( + VisitName(node.Left), + Visit(node.Right)); + } + else + { + throw new NotImplementedException(); + } + } + + private VB.Syntax.TypeArgumentListSyntax ConvertTypeArguments( + CS.Syntax.TypeArgumentListSyntax typeArgumentList) + { + VB.Syntax.TypeSyntax[] types = typeArgumentList.Arguments.Select(VisitType).ToArray(); + return VB.SyntaxFactory.TypeArgumentList(types); + } + + public override SyntaxNode VisitTypeParameterList(CS.Syntax.TypeParameterListSyntax node) + { + VB.Syntax.TypeParameterSyntax[] parameters = node.Parameters.Select(Visit).ToArray(); + return VB.SyntaxFactory.TypeParameterList(parameters); + } + + private VB.Syntax.TypeParameterListSyntax ConvertTypeParameters(SeparatedSyntaxList list) + { + VB.Syntax.TypeParameterSyntax[] parameters = list.Select(t => + { + SyntaxToken variance = t.VarianceKeyword.IsKind(CS.SyntaxKind.None) + ? new SyntaxToken() + : t.VarianceKeyword.IsKind(CS.SyntaxKind.InKeyword) + ? VB.SyntaxFactory.Token(VB.SyntaxKind.InKeyword) + : VB.SyntaxFactory.Token(VB.SyntaxKind.OutKeyword); + + // TODO: get the constraints. + return VB.SyntaxFactory.TypeParameter(ConvertIdentifier(t.Identifier)).WithVarianceKeyword(variance); + }).ToArray(); + + return VB.SyntaxFactory.TypeParameterList(parameters); + } + + public override SyntaxNode VisitNamespaceDeclaration(CS.Syntax.NamespaceDeclarationSyntax node) + { + return VB.SyntaxFactory.NamespaceBlock( + VB.SyntaxFactory.NamespaceStatement(VisitName(node.Name)), + List(node.Members.Select(Visit))); + } + + public override SyntaxNode VisitEnumDeclaration(CS.Syntax.EnumDeclarationSyntax node) + { + VB.Syntax.EnumStatementSyntax declaration = VB.SyntaxFactory.EnumStatement( + ConvertAttributes(node.AttributeLists), + ConvertModifiers(node.Modifiers), + ConvertIdentifier(node.Identifier), + underlyingType: null); + + return VB.SyntaxFactory.EnumBlock( + declaration, + List(node.Members.Select(Visit))); + } + + public override SyntaxNode VisitClassDeclaration(CS.Syntax.ClassDeclarationSyntax node) + { + VB.SyntaxKind blockKind; + VB.SyntaxKind declarationKind; + VB.SyntaxKind endKind; + SyntaxToken keyword; + SyntaxList inherits = List(); + SyntaxList implements = List(); + + if (node.Modifiers.Any(CS.SyntaxKind.StaticKeyword)) + { + blockKind = VB.SyntaxKind.ModuleBlock; + declarationKind = VB.SyntaxKind.ModuleStatement; + endKind = VB.SyntaxKind.EndModuleStatement; + keyword = VB.SyntaxFactory.Token(VB.SyntaxKind.ModuleKeyword); + } + else + { + blockKind = VB.SyntaxKind.ClassBlock; + declarationKind = VB.SyntaxKind.ClassStatement; + endKind = VB.SyntaxKind.EndClassStatement; + keyword = VB.SyntaxFactory.Token(VB.SyntaxKind.ClassKeyword); + } + + if (node.BaseList != null && node.BaseList.Types.Count >= 1) + { + // hack. in C# it's just a list of types. We can't tell if the first one is a + // class or not. So we just check if it starts with a capital I or not and use + // that as a weak enough heuristic. + TypeSyntax firstType = node.BaseList.Types[0].Type; + SyntaxToken rightName = GetRightmostNamePart(firstType); + if (rightName.ValueText.Length >= 2 && + rightName.ValueText[0] == 'I' && + char.IsUpper(rightName.ValueText[1])) + { + implements = ConvertImplementsList(node.BaseList.Types.Select(t => t.Type)); + } + else + { + // first type looks like a class + inherits = List( + VB.SyntaxFactory.InheritsStatement(VB.SyntaxFactory.Token(VB.SyntaxKind.InheritsKeyword), SeparatedList(VisitType(firstType)))); + + implements = ConvertImplementsList(node.BaseList.Types.Skip(1).Select(t => t.Type)); + } + } + + return VisitTypeDeclaration(node, blockKind, declarationKind, endKind, keyword, inherits, implements); + } + + public override SyntaxNode VisitStructDeclaration(CS.Syntax.StructDeclarationSyntax node) + { + VB.SyntaxKind blockKind = VB.SyntaxKind.StructureBlock; + VB.SyntaxKind declarationKind = VB.SyntaxKind.StructureStatement; + VB.SyntaxKind endKind = VB.SyntaxKind.EndStructureStatement; + SyntaxToken keyword = VB.SyntaxFactory.Token(VB.SyntaxKind.StructureKeyword); + SyntaxList implements = List(); + if (node.BaseList != null) + { + implements = ConvertImplementsList(node.BaseList.Types.Select(t => t.Type)); + } + + return VisitTypeDeclaration(node, blockKind, declarationKind, endKind, keyword, inherits: List(), implements: implements); + } + + public override SyntaxNode VisitInterfaceDeclaration(CS.Syntax.InterfaceDeclarationSyntax node) + { + VB.SyntaxKind blockKind = VB.SyntaxKind.InterfaceBlock; + VB.SyntaxKind declarationKind = VB.SyntaxKind.InterfaceStatement; + VB.SyntaxKind endKind = VB.SyntaxKind.EndInterfaceStatement; + SyntaxToken keyword = VB.SyntaxFactory.Token(VB.SyntaxKind.InterfaceKeyword); + SyntaxList inherits = List(); + if (node.BaseList != null) + { + inherits = ConvertInheritsList(node.BaseList.Types.Select(t => t.Type)); + } + + return VisitTypeDeclaration(node, blockKind, declarationKind, endKind, keyword, inherits: inherits, implements: List()); + } + + private SyntaxNode VisitTypeDeclaration( + CS.Syntax.TypeDeclarationSyntax node, + VB.SyntaxKind blockKind, VB.SyntaxKind declarationKind, VB.SyntaxKind endKind, + SyntaxToken keyword, SyntaxList inherits, SyntaxList implements) + { + SyntaxToken identifier = ConvertIdentifier(node.Identifier); + VB.Syntax.TypeParameterListSyntax typeParameters = Visit(node.TypeParameterList); + + VB.Syntax.TypeStatementSyntax declaration = VB.SyntaxFactory.TypeStatement( + declarationKind, + ConvertAttributes(node.AttributeLists), + ConvertModifiers(node.Modifiers.Where(t => !t.IsKind(CS.SyntaxKind.StaticKeyword))), + keyword, + identifier, + typeParameters); + + VB.Syntax.TypeBlockSyntax typeBlock = VB.SyntaxFactory.TypeBlock( + blockKind, + declaration, + inherits, + implements, + List(node.Members.Select(Visit)), + VB.SyntaxFactory.EndBlockStatement(endKind, VB.SyntaxFactory.Token(VB.SyntaxKind.EndKeyword), keyword)); + + SyntaxTrivia docComment = node.GetLeadingTrivia().FirstOrDefault(t => CS.SyntaxFacts.IsDocumentationCommentTrivia(t.Kind())); + if (!docComment.IsKind(CS.SyntaxKind.None)) + { + IEnumerable vbDocComment = VisitTrivia(docComment); + return typeBlock.WithLeadingTrivia(typeBlock.GetLeadingTrivia().Concat(vbDocComment)); + } + else + { + return typeBlock; + } + } + + private SyntaxToken GetRightmostNamePart(CS.Syntax.TypeSyntax type) + { + while (true) + { + if (type.IsKind(CS.SyntaxKind.IdentifierName)) + { + return ((CS.Syntax.IdentifierNameSyntax)type).Identifier; + } + else if (type.IsKind(CS.SyntaxKind.QualifiedName)) + { + type = ((CS.Syntax.QualifiedNameSyntax)type).Right; + } + else if (type.IsKind(CS.SyntaxKind.GenericName)) + { + return ((CS.Syntax.GenericNameSyntax)type).Identifier; + } + else if (type.IsKind(CS.SyntaxKind.AliasQualifiedName)) + { + type = ((CS.Syntax.AliasQualifiedNameSyntax)type).Name; + } + else + { + System.Diagnostics.Debug.Fail("Unexpected type syntax kind."); + return default(SyntaxToken); + } + } + } + + private SyntaxList ConvertImplementsList(IEnumerable types) + => List(types.Select(t => VB.SyntaxFactory.ImplementsStatement(VisitType(t)))); + + private SyntaxList ConvertInheritsList(IEnumerable types) + => List(types.Select(t => VB.SyntaxFactory.InheritsStatement(VisitType(t)))); + + private SyntaxTokenList TokenList(IEnumerable tokens) => VB.SyntaxFactory.TokenList(tokens); + + internal SyntaxTokenList ConvertModifiers(IEnumerable list) + => TokenList(list.Where(t => !t.IsKind(CS.SyntaxKind.ThisKeyword)).Select(VisitToken)); + + public override SyntaxNode VisitMethodDeclaration(CS.Syntax.MethodDeclarationSyntax node) + { + bool isVoid = node.ReturnType.IsKind(CS.SyntaxKind.PredefinedType) && + ((CS.Syntax.PredefinedTypeSyntax)node.ReturnType).Keyword.IsKind(CS.SyntaxKind.VoidKeyword); + + VB.Syntax.ImplementsClauseSyntax implementsClause = null; + if (node.ExplicitInterfaceSpecifier != null) + { + implementsClause = VB.SyntaxFactory.ImplementsClause( + VB.SyntaxFactory.QualifiedName( + (VB.Syntax.NameSyntax)VisitType(node.ExplicitInterfaceSpecifier.Name), + VB.SyntaxFactory.IdentifierName(VisitToken(node.Identifier)))); + } + + VB.Syntax.MethodStatementSyntax begin; + + SyntaxToken identifier = ConvertIdentifier(node.Identifier); + VB.Syntax.TypeParameterListSyntax typeParameters = Visit(node.TypeParameterList); + + bool isExtension = + node.ParameterList.Parameters.Count > 0 && + node.ParameterList.Parameters[0].Modifiers.Any(CS.SyntaxKind.ThisKeyword); + + List modifiers = isExtension + ? node.Modifiers.Where(t => !t.IsKind(CS.SyntaxKind.StaticKeyword)).ToList() + : node.Modifiers.ToList(); + + List attributes = isExtension + ? node.AttributeLists.Concat(CreateExtensionAttribute()).ToList() + : node.AttributeLists.ToList(); + + if (isVoid) + { + begin = VB.SyntaxFactory.SubStatement( + ConvertAttributes(attributes), + ConvertModifiers(modifiers), + identifier, + typeParameters, + Visit(node.ParameterList), + asClause: null, + handlesClause: null, + implementsClause: implementsClause); + } + else + { + SplitAttributes(attributes, out SyntaxList returnAttributes, out SyntaxList remainAttributes); + + begin = VB.SyntaxFactory.FunctionStatement( + remainAttributes, + ConvertModifiers(modifiers), + identifier, + typeParameters, + Visit(node.ParameterList), + VB.SyntaxFactory.SimpleAsClause(returnAttributes, VisitType(node.ReturnType)), + handlesClause: null, + implementsClause: implementsClause); + } + + SyntaxTrivia docComment = node.GetLeadingTrivia().FirstOrDefault(t => CS.SyntaxFacts.IsDocumentationCommentTrivia(t.Kind())); + if (!docComment.IsKind(CS.SyntaxKind.None)) + { + IEnumerable vbDocComment = VisitTrivia(docComment); + begin = begin.WithLeadingTrivia(begin.GetLeadingTrivia().Concat(vbDocComment)); + } + + if (node.Body == null) + { + return begin; + } + + if (isVoid) + { + return VB.SyntaxFactory.SubBlock( + begin, + statementVisitor.VisitStatement(node.Body), + VB.SyntaxFactory.EndSubStatement()); + } + else + { + return VB.SyntaxFactory.FunctionBlock( + begin, + statementVisitor.VisitStatement(node.Body), + VB.SyntaxFactory.EndFunctionStatement()); + } + } + + private CS.Syntax.AttributeListSyntax CreateExtensionAttribute() + => CS.SyntaxFactory.AttributeList( + attributes: CS.SyntaxFactory.SingletonSeparatedList( + CS.SyntaxFactory.Attribute(CS.SyntaxFactory.ParseName("System.Runtime.CompilerServices.Extension")))); + + private void SplitAttributes( + IList attributes, + out SyntaxList returnAttributes, + out SyntaxList remainingAttributes) + { + AttributeListSyntax returnAttribute = + attributes.FirstOrDefault(a => a.Target != null && a.Target.Identifier.IsKind(CS.SyntaxKind.ReturnKeyword)); + + IEnumerable rest = + attributes.Where(a => a != returnAttribute); + + returnAttributes = List(Visit(returnAttribute)); + remainingAttributes = ConvertAttributes(rest); + } + + public override SyntaxNode VisitParameterList(CS.Syntax.ParameterListSyntax node) + => VB.SyntaxFactory.ParameterList( + SeparatedCommaList(node.Parameters.Select(Visit))); + + public override SyntaxNode VisitBracketedParameterList(CS.Syntax.BracketedParameterListSyntax node) + => VB.SyntaxFactory.ParameterList( + SeparatedCommaList(node.Parameters.Select(Visit))); + + public override SyntaxNode VisitParameter(CS.Syntax.ParameterSyntax node) + { + VB.Syntax.SimpleAsClauseSyntax asClause = node.Type == null + ? null + : VB.SyntaxFactory.SimpleAsClause(VisitType(node.Type)); + SyntaxTokenList modifiers = ConvertModifiers(node.Modifiers); + if (node.Default != null) + { + modifiers = TokenList(modifiers.Concat(VB.SyntaxFactory.Token(VB.SyntaxKind.OptionalKeyword))); + } + + return VB.SyntaxFactory.Parameter( + ConvertAttributes(node.AttributeLists), + modifiers, + VB.SyntaxFactory.ModifiedIdentifier(ConvertIdentifier(node.Identifier)), + asClause, + node.Default == null ? null : VB.SyntaxFactory.EqualsValue(VisitExpression(node.Default.Value))); + } + + public override SyntaxNode VisitGenericName(CS.Syntax.GenericNameSyntax node) + => VB.SyntaxFactory.GenericName( + ConvertIdentifier(node.Identifier), + ConvertTypeArguments(node.TypeArgumentList)); + + public override SyntaxNode VisitTypeParameter(CS.Syntax.TypeParameterSyntax node) + { + SyntaxToken variance = node.VarianceKeyword.IsKind(CS.SyntaxKind.None) + ? default(SyntaxToken) + : node.VarianceKeyword.IsKind(CS.SyntaxKind.InKeyword) + ? VB.SyntaxFactory.Token(VB.SyntaxKind.InKeyword) + : VB.SyntaxFactory.Token(VB.SyntaxKind.OutKeyword); + + // TODO: get the constraints. + return VB.SyntaxFactory.TypeParameter(ConvertIdentifier(node.Identifier)).WithVarianceKeyword(variance); + } + + public override SyntaxNode VisitPredefinedType(CS.Syntax.PredefinedTypeSyntax node) + { + switch (node.Keyword.Kind()) + { + case CS.SyntaxKind.BoolKeyword: + return VB.SyntaxFactory.PredefinedType(VB.SyntaxFactory.Token(VB.SyntaxKind.BooleanKeyword)); + case CS.SyntaxKind.ByteKeyword: + return VB.SyntaxFactory.PredefinedType(VB.SyntaxFactory.Token(VB.SyntaxKind.ByteKeyword)); + case CS.SyntaxKind.CharKeyword: + return VB.SyntaxFactory.PredefinedType(VB.SyntaxFactory.Token(VB.SyntaxKind.CharKeyword)); + case CS.SyntaxKind.DecimalKeyword: + return VB.SyntaxFactory.PredefinedType(VB.SyntaxFactory.Token(VB.SyntaxKind.DecimalKeyword)); + case CS.SyntaxKind.DoubleKeyword: + return VB.SyntaxFactory.PredefinedType(VB.SyntaxFactory.Token(VB.SyntaxKind.DoubleKeyword)); + case CS.SyntaxKind.FloatKeyword: + return VB.SyntaxFactory.PredefinedType(VB.SyntaxFactory.Token(VB.SyntaxKind.SingleKeyword)); + case CS.SyntaxKind.IntKeyword: + return VB.SyntaxFactory.PredefinedType(VB.SyntaxFactory.Token(VB.SyntaxKind.IntegerKeyword)); + case CS.SyntaxKind.LongKeyword: + return VB.SyntaxFactory.PredefinedType(VB.SyntaxFactory.Token(VB.SyntaxKind.LongKeyword)); + case CS.SyntaxKind.ObjectKeyword: + return VB.SyntaxFactory.PredefinedType(VB.SyntaxFactory.Token(VB.SyntaxKind.ObjectKeyword)); + case CS.SyntaxKind.SByteKeyword: + return VB.SyntaxFactory.PredefinedType(VB.SyntaxFactory.Token(VB.SyntaxKind.SByteKeyword)); + case CS.SyntaxKind.ShortKeyword: + return VB.SyntaxFactory.PredefinedType(VB.SyntaxFactory.Token(VB.SyntaxKind.ShortKeyword)); + case CS.SyntaxKind.StringKeyword: + return VB.SyntaxFactory.PredefinedType(VB.SyntaxFactory.Token(VB.SyntaxKind.StringKeyword)); + case CS.SyntaxKind.UIntKeyword: + return VB.SyntaxFactory.PredefinedType(VB.SyntaxFactory.Token(VB.SyntaxKind.UIntegerKeyword)); + case CS.SyntaxKind.ULongKeyword: + return VB.SyntaxFactory.PredefinedType(VB.SyntaxFactory.Token(VB.SyntaxKind.ULongKeyword)); + case CS.SyntaxKind.UShortKeyword: + return VB.SyntaxFactory.PredefinedType(VB.SyntaxFactory.Token(VB.SyntaxKind.UShortKeyword)); + case CS.SyntaxKind.VoidKeyword: + return VB.SyntaxFactory.IdentifierName(VB.SyntaxFactory.Identifier("Void")); + default: + throw new NotImplementedException(); + } + } + + public override SyntaxNode VisitBaseExpression(CS.Syntax.BaseExpressionSyntax node) + => VB.SyntaxFactory.MyBaseExpression(VB.SyntaxFactory.Token(VB.SyntaxKind.MyBaseKeyword)); + + public override SyntaxNode VisitThisExpression(CS.Syntax.ThisExpressionSyntax node) + => VB.SyntaxFactory.MeExpression(VB.SyntaxFactory.Token(VB.SyntaxKind.MeKeyword)); + + public override SyntaxNode VisitLiteralExpression(CS.Syntax.LiteralExpressionSyntax node) + { + switch (node.Kind()) + { + case CS.SyntaxKind.ArgListExpression: + string error = CreateCouldNotBeConvertedString(node.ToFullString(), typeof(SyntaxNode)); + return VB.SyntaxFactory.StringLiteralExpression( + VB.SyntaxFactory.StringLiteralToken(error, error)); + case CS.SyntaxKind.BaseExpression: + return VB.SyntaxFactory.MyBaseExpression(VB.SyntaxFactory.Token(VB.SyntaxKind.MyBaseKeyword)); + case CS.SyntaxKind.NumericLiteralExpression: + case CS.SyntaxKind.StringLiteralExpression: + case CS.SyntaxKind.CharacterLiteralExpression: + case CS.SyntaxKind.TrueLiteralExpression: + case CS.SyntaxKind.FalseLiteralExpression: + case CS.SyntaxKind.NullLiteralExpression: + return ConvertLiteralExpression(node); + } + + throw new NotImplementedException(); + } + + private SyntaxNode ConvertLiteralExpression(CS.Syntax.LiteralExpressionSyntax node) + { + switch (node.Token.Kind()) + { + case CS.SyntaxKind.CharacterLiteralToken: + return VB.SyntaxFactory.CharacterLiteralExpression( + VB.SyntaxFactory.CharacterLiteralToken("\"" + node.Token.ToString().Substring(1, Math.Max(node.Token.ToString().Length - 2, 0)) + "\"c", (char)node.Token.Value)); + case CS.SyntaxKind.FalseKeyword: + return VB.SyntaxFactory.FalseLiteralExpression(VB.SyntaxFactory.Token(VB.SyntaxKind.FalseKeyword)); + case CS.SyntaxKind.NullKeyword: + return VB.SyntaxFactory.NothingLiteralExpression(VB.SyntaxFactory.Token(VB.SyntaxKind.NothingKeyword)); + case CS.SyntaxKind.NumericLiteralToken: + return ConvertNumericLiteralToken(node); + case CS.SyntaxKind.StringLiteralToken: + return ConvertStringLiteralExpression(node); + case CS.SyntaxKind.TrueKeyword: + return VB.SyntaxFactory.TrueLiteralExpression(VB.SyntaxFactory.Token(VB.SyntaxKind.TrueKeyword)); + } + + throw new NotImplementedException(); + } + + private SyntaxNode ConvertNumericLiteralToken(CS.Syntax.LiteralExpressionSyntax node) + { + if (node.Token.ToString().StartsWith("0x") || + node.Token.ToString().StartsWith("0X")) + { + return VB.SyntaxFactory.NumericLiteralExpression( + VB.SyntaxFactory.IntegerLiteralToken( + "&H" + node.Token.ToString().Substring(2).ToUpperInvariant(), + VB.Syntax.LiteralBase.Hexadecimal, + VB.Syntax.TypeCharacter.None, + 0)); + } + + // TODO: handle the other numeric types. + return VB.SyntaxFactory.NumericLiteralExpression( + VB.SyntaxFactory.IntegerLiteralToken(node.Token.ToString(), VB.Syntax.LiteralBase.Decimal, VB.Syntax.TypeCharacter.None, 0)); + } + + private SyntaxNode ConvertStringLiteralExpression(CS.Syntax.LiteralExpressionSyntax node) + { + int start = this.text.Lines.IndexOf(node.Token.Span.Start); + int end = this.text.Lines.IndexOf(node.Token.Span.End); + + if (node.Token.IsVerbatimStringLiteral() && + start != end) + { + string text = node.Token.ToString(); + text = text.Substring(2, text.Length - 3); + text = System.Security.SecurityElement.Escape(text); + + return VB.SyntaxFactory.SimpleMemberAccessExpression( + VB.SyntaxFactory.XmlElement( + VB.SyntaxFactory.XmlElementStartTag( + VB.SyntaxFactory.Token(VB.SyntaxKind.LessThanToken), + VB.SyntaxFactory.XmlName(null, VB.SyntaxFactory.XmlNameToken("text", VB.SyntaxKind.XmlNameToken)), + VB.SyntaxFactory.List(), + VB.SyntaxFactory.Token(VB.SyntaxKind.GreaterThanToken)), + List(VB.SyntaxFactory.XmlText(VB.SyntaxFactory.TokenList(VB.SyntaxFactory.XmlTextLiteralToken(text, text)))), + VB.SyntaxFactory.XmlElementEndTag( + VB.SyntaxFactory.Token(VB.SyntaxKind.LessThanSlashToken), + VB.SyntaxFactory.XmlName(null, VB.SyntaxFactory.XmlNameToken("text", VB.SyntaxKind.XmlNameToken)), + VB.SyntaxFactory.Token(VB.SyntaxKind.GreaterThanToken))), + VB.SyntaxFactory.Token(VB.SyntaxKind.DotToken), + VB.SyntaxFactory.IdentifierName(VB.SyntaxFactory.Identifier("Value"))); + } + else + { + return VB.SyntaxFactory.StringLiteralExpression(VisitToken(node.Token)); + } + } + + public override SyntaxNode VisitVariableDeclarator(CS.Syntax.VariableDeclaratorSyntax node) + { + TypeSyntax type = node.GetVariableType(); + bool isVar = type is CS.Syntax.IdentifierNameSyntax && ((CS.Syntax.IdentifierNameSyntax)type).Identifier.ValueText == "var"; + + VB.Syntax.EqualsValueSyntax initializer = null; + if (node.Initializer != null) + { + initializer = VB.SyntaxFactory.EqualsValue(VisitExpression(node.Initializer.Value)); + } + + return VB.SyntaxFactory.VariableDeclarator( + SeparatedList(VB.SyntaxFactory.ModifiedIdentifier(ConvertIdentifier(node.Identifier))), + isVar ? null : VB.SyntaxFactory.SimpleAsClause(VisitType(type)), + initializer); + } + + public override SyntaxNode VisitObjectCreationExpression(CS.Syntax.ObjectCreationExpressionSyntax node) + => VB.SyntaxFactory.ObjectCreationExpression( + default(SyntaxList), + VisitType(node.Type), + Visit(node.ArgumentList), + Visit(node.Initializer)); + + public override SyntaxNode VisitArgumentList(CS.Syntax.ArgumentListSyntax node) + => VB.SyntaxFactory.ArgumentList( + SeparatedCommaList(node.Arguments.Select(Visit))); + + public override SyntaxNode VisitBracketedArgumentList(CS.Syntax.BracketedArgumentListSyntax node) + => VB.SyntaxFactory.ArgumentList( + SeparatedCommaList(node.Arguments.Select(Visit))); + + public override SyntaxNode VisitArgument(CS.Syntax.ArgumentSyntax node) + { + if (node.NameColon == null) + { + return VB.SyntaxFactory.SimpleArgument(VisitExpression(node.Expression)); + } + else + { + return VB.SyntaxFactory.SimpleArgument( + VB.SyntaxFactory.NameColonEquals( + VB.SyntaxFactory.IdentifierName(ConvertIdentifier(node.NameColon.Name))), + VisitExpression(node.Expression)); + } + } + + public override SyntaxNode VisitInvocationExpression(CS.Syntax.InvocationExpressionSyntax node) + => VB.SyntaxFactory.InvocationExpression( + VisitExpression(node.Expression), + Visit(node.ArgumentList)); + + public override SyntaxNode VisitFieldDeclaration(CS.Syntax.FieldDeclarationSyntax node) + { + SyntaxTokenList modifiers = ConvertModifiers(node.Modifiers); + if (modifiers.Count == 0) + { + modifiers = VB.SyntaxFactory.TokenList(VB.SyntaxFactory.Token(VB.SyntaxKind.DimKeyword)); + } + + return VB.SyntaxFactory.FieldDeclaration( + ConvertAttributes(node.AttributeLists), + modifiers, + SeparatedCommaList(node.Declaration.Variables.Select(Visit))); + } + + public override SyntaxNode VisitConstructorDeclaration(CS.Syntax.ConstructorDeclarationSyntax node) + { + VB.Syntax.SubNewStatementSyntax declaration = VB.SyntaxFactory.SubNewStatement( + ConvertAttributes(node.AttributeLists), + ConvertModifiers(node.Modifiers), + Visit(node.ParameterList)); + + if (node.Body == null) + { + return declaration; + } + + return VB.SyntaxFactory.ConstructorBlock( + declaration, + statementVisitor.VisitStatement(node.Body), + VB.SyntaxFactory.EndSubStatement()); + } + + public override SyntaxNode VisitMemberAccessExpression(CS.Syntax.MemberAccessExpressionSyntax node) + { + if (node.Name.IsKind(CS.SyntaxKind.IdentifierName)) + { + return VB.SyntaxFactory.SimpleMemberAccessExpression( + VisitExpression(node.Expression), + VB.SyntaxFactory.Token(VB.SyntaxKind.DotToken), + VB.SyntaxFactory.IdentifierName(ConvertIdentifier((CS.Syntax.IdentifierNameSyntax)node.Name))); + } + else if (node.Name.IsKind(CS.SyntaxKind.GenericName)) + { + GenericNameSyntax genericName = (CS.Syntax.GenericNameSyntax)node.Name; + return VB.SyntaxFactory.SimpleMemberAccessExpression( + VisitExpression(node.Expression), + VB.SyntaxFactory.Token(VB.SyntaxKind.DotToken), + VB.SyntaxFactory.GenericName(ConvertIdentifier(genericName.Identifier), + ConvertTypeArguments(genericName.TypeArgumentList))); + } + else + { + throw new NotImplementedException(); + } + } + + public override SyntaxNode VisitBinaryExpression(CS.Syntax.BinaryExpressionSyntax node) + { + VB.Syntax.ExpressionSyntax left = VisitExpression(node.Left); + VB.Syntax.ExpressionSyntax right = VisitExpression(node.Right); + + switch (node.OperatorToken.Kind()) + { + case CS.SyntaxKind.AmpersandAmpersandToken: + return VB.SyntaxFactory.AndAlsoExpression(left, right); + case CS.SyntaxKind.AmpersandToken: + return VB.SyntaxFactory.AndExpression(left, right); + + case CS.SyntaxKind.AsKeyword: + return VB.SyntaxFactory.TryCastExpression(left, (VB.Syntax.TypeSyntax)right); + case CS.SyntaxKind.AsteriskToken: + return VB.SyntaxFactory.MultiplyExpression(left, right); + case CS.SyntaxKind.AsteriskEqualsToken: + return VB.SyntaxFactory.MultiplyAssignmentStatement(left, VB.SyntaxFactory.Token(VB.SyntaxKind.AsteriskEqualsToken), right); + case CS.SyntaxKind.BarToken: + return VB.SyntaxFactory.OrExpression(left, right); + case CS.SyntaxKind.BarBarToken: + return VB.SyntaxFactory.OrElseExpression(left, right); + + case CS.SyntaxKind.CaretToken: + return VB.SyntaxFactory.ExclusiveOrExpression(left, right); + + case CS.SyntaxKind.MinusToken: + return VB.SyntaxFactory.SubtractExpression(left, right); + + case CS.SyntaxKind.EqualsEqualsToken: + if (node.Right.IsKind(CS.SyntaxKind.NullLiteralExpression)) + { + return VB.SyntaxFactory.IsExpression(left, right); + } + else + { + return VB.SyntaxFactory.EqualsExpression(left, right); + } + + case CS.SyntaxKind.ExclamationEqualsToken: + if (node.Right.IsKind(CS.SyntaxKind.NullLiteralExpression)) + { + return VB.SyntaxFactory.IsNotExpression(left, right); + } + else + { + return VB.SyntaxFactory.NotEqualsExpression(left, right); + } + + case CS.SyntaxKind.GreaterThanToken: + return VB.SyntaxFactory.GreaterThanExpression(left, right); + case CS.SyntaxKind.GreaterThanEqualsToken: + return VB.SyntaxFactory.GreaterThanOrEqualExpression(left, right); + case CS.SyntaxKind.GreaterThanGreaterThanToken: + return VB.SyntaxFactory.RightShiftExpression(left, right); + + case CS.SyntaxKind.IsKeyword: + return VB.SyntaxFactory.TypeOfIsExpression(left, (VB.Syntax.TypeSyntax)right); + + case CS.SyntaxKind.LessThanToken: + return VB.SyntaxFactory.LessThanExpression(left, right); + case CS.SyntaxKind.LessThanEqualsToken: + return VB.SyntaxFactory.LessThanOrEqualExpression(left, right); + case CS.SyntaxKind.LessThanLessThanToken: + return VB.SyntaxFactory.LeftShiftExpression(left, right); + case CS.SyntaxKind.PercentToken: + return VB.SyntaxFactory.ModuloExpression(left, right); + + case CS.SyntaxKind.PlusToken: + return VB.SyntaxFactory.AddExpression(left, right); + case CS.SyntaxKind.QuestionToken: + case CS.SyntaxKind.QuestionQuestionToken: + { + VB.Syntax.ArgumentSyntax[] args = new VB.Syntax.ArgumentSyntax[] { VB.SyntaxFactory.SimpleArgument(left), VB.SyntaxFactory.SimpleArgument(right) }; + SeparatedSyntaxList arguments = SeparatedCommaList(args); + return VB.SyntaxFactory.InvocationExpression( + VB.SyntaxFactory.IdentifierName(VB.SyntaxFactory.Identifier("If")), + VB.SyntaxFactory.ArgumentList(arguments)); + } + + case CS.SyntaxKind.SlashToken: + return VB.SyntaxFactory.DivideExpression(left, right); + } + + throw new NotImplementedException(); + } + + public override SyntaxNode VisitAssignmentExpression(CS.Syntax.AssignmentExpressionSyntax node) + { + VB.Syntax.ExpressionSyntax left = VisitExpression(node.Left); + VB.Syntax.ExpressionSyntax right = VisitExpression(node.Right); + + switch (node.OperatorToken.Kind()) + { + case CS.SyntaxKind.AmpersandEqualsToken: + { + VB.Syntax.ExpressionSyntax left2 = VisitExpression(node.Left); + return VB.SyntaxFactory.SimpleAssignmentStatement( + left, + VB.SyntaxFactory.Token(VB.SyntaxKind.EqualsToken), + VB.SyntaxFactory.AndAlsoExpression(left2, right)); + } + + case CS.SyntaxKind.AsteriskEqualsToken: + return VB.SyntaxFactory.MultiplyAssignmentStatement(left, VB.SyntaxFactory.Token(VB.SyntaxKind.AsteriskEqualsToken), right); + + case CS.SyntaxKind.BarEqualsToken: + { + VB.Syntax.ExpressionSyntax left2 = VisitExpression(node.Left); + return VB.SyntaxFactory.SimpleAssignmentStatement( + left, + VB.SyntaxFactory.Token(VB.SyntaxKind.EqualsToken), + VB.SyntaxFactory.OrElseExpression(left2, right)); + } + + case CS.SyntaxKind.CaretEqualsToken: + { + VB.Syntax.ExpressionSyntax left2 = VisitExpression(node.Left); + return VB.SyntaxFactory.SimpleAssignmentStatement( + left, + VB.SyntaxFactory.Token(VB.SyntaxKind.EqualsToken), + VB.SyntaxFactory.ExclusiveOrExpression(left2, right)); + } + + case CS.SyntaxKind.MinusEqualsToken: + return VB.SyntaxFactory.SubtractAssignmentStatement(left, VB.SyntaxFactory.Token(VB.SyntaxKind.MinusEqualsToken), right); + + case CS.SyntaxKind.EqualsToken: + return VB.SyntaxFactory.SimpleAssignmentStatement(left, VB.SyntaxFactory.Token(VB.SyntaxKind.EqualsToken), right); + + case CS.SyntaxKind.GreaterThanGreaterThanEqualsToken: + { + VB.Syntax.ExpressionSyntax left2 = VisitExpression(node.Left); + return VB.SyntaxFactory.SimpleAssignmentStatement( + left, + VB.SyntaxFactory.Token(VB.SyntaxKind.EqualsToken), + VB.SyntaxFactory.RightShiftExpression(left2, right)); + } + + case CS.SyntaxKind.LessThanLessThanEqualsToken: + { + VB.Syntax.ExpressionSyntax left2 = VisitExpression(node.Left); + return VB.SyntaxFactory.SimpleAssignmentStatement( + left, + VB.SyntaxFactory.Token(VB.SyntaxKind.EqualsToken), + VB.SyntaxFactory.LeftShiftExpression(left2, right)); + } + + case CS.SyntaxKind.PercentEqualsToken: + { + VB.Syntax.ExpressionSyntax left2 = VisitExpression(node.Left); + return VB.SyntaxFactory.SimpleAssignmentStatement( + left, + VB.SyntaxFactory.Token(VB.SyntaxKind.EqualsToken), + VB.SyntaxFactory.ModuloExpression(left2, right)); + } + + case CS.SyntaxKind.PlusEqualsToken: + return VB.SyntaxFactory.AddAssignmentStatement(left, VB.SyntaxFactory.Token(VB.SyntaxKind.PlusEqualsToken), right); + + case CS.SyntaxKind.SlashEqualsToken: + return VB.SyntaxFactory.DivideAssignmentStatement(left, VB.SyntaxFactory.Token(VB.SyntaxKind.SlashEqualsToken), right); + } + + throw new NotImplementedException(); + } + + public override SyntaxNode VisitElseClause(CS.Syntax.ElseClauseSyntax node) + => VB.SyntaxFactory.ElseBlock( + VB.SyntaxFactory.ElseStatement(), + statementVisitor.VisitStatement(node.Statement)); + + public override SyntaxNode VisitSwitchSection(CS.Syntax.SwitchSectionSyntax node) + => VB.SyntaxFactory.CaseBlock( + VB.SyntaxFactory.CaseStatement( + SeparatedCommaList(node.Labels.Select(Visit))), + List( + node.Statements.SelectMany(statementVisitor.VisitStatementEnumerable))); + + public override SyntaxNode VisitCaseSwitchLabel(CaseSwitchLabelSyntax node) + => VB.SyntaxFactory.SimpleCaseClause(VisitExpression(node.Value)); + + public override SyntaxNode VisitDefaultSwitchLabel(DefaultSwitchLabelSyntax node) + => VB.SyntaxFactory.ElseCaseClause(); + + public override SyntaxNode VisitCastExpression(CS.Syntax.CastExpressionSyntax node) + { + // todo: need to handle CInt and all those other cases. + return VB.SyntaxFactory.DirectCastExpression( + VisitExpression(node.Expression), + VisitType(node.Type)); + } + + public override SyntaxNode VisitParenthesizedLambdaExpression(CS.Syntax.ParenthesizedLambdaExpressionSyntax node) + { + VB.Syntax.ParameterListSyntax parameters = Visit(node.ParameterList); + VB.Syntax.LambdaHeaderSyntax lambdaHeader = VB.SyntaxFactory.FunctionLambdaHeader( + new SyntaxList(), + node.AsyncKeyword.IsKind(CS.SyntaxKind.None) ? VB.SyntaxFactory.TokenList() : VB.SyntaxFactory.TokenList(VisitToken(node.AsyncKeyword)), + parameters, + null); + if (node.Body.IsKind(CS.SyntaxKind.Block)) + { + return VB.SyntaxFactory.MultiLineFunctionLambdaExpression( + lambdaHeader, + statementVisitor.VisitStatement((CS.Syntax.BlockSyntax)node.Body), + VB.SyntaxFactory.EndFunctionStatement()); + } + else + { + return VB.SyntaxFactory.SingleLineFunctionLambdaExpression( + lambdaHeader, + (VB.VisualBasicSyntaxNode)Visit(node.Body)); + } + } + + public override SyntaxNode VisitSimpleLambdaExpression(CS.Syntax.SimpleLambdaExpressionSyntax node) + { + VB.Syntax.ParameterSyntax parameter = VB.SyntaxFactory.Parameter( + VB.SyntaxFactory.ModifiedIdentifier( + ConvertIdentifier(node.Parameter.Identifier))); + + VB.Syntax.LambdaHeaderSyntax lambdaHeader = VB.SyntaxFactory.FunctionLambdaHeader( + new SyntaxList(), + node.AsyncKeyword.IsKind(CS.SyntaxKind.None) ? VB.SyntaxFactory.TokenList() : VB.SyntaxFactory.TokenList(VisitToken(node.AsyncKeyword)), + VB.SyntaxFactory.ParameterList(SeparatedList(parameter)), + null); + if (node.Body.IsKind(CS.SyntaxKind.Block)) + { + return VB.SyntaxFactory.MultiLineFunctionLambdaExpression( + lambdaHeader, + statementVisitor.VisitStatement((CS.Syntax.BlockSyntax)node.Body), + VB.SyntaxFactory.EndFunctionStatement()); + } + else + { + return VB.SyntaxFactory.SingleLineFunctionLambdaExpression( + lambdaHeader, + (VB.VisualBasicSyntaxNode)Visit(node.Body)); + } + } + + public override SyntaxNode VisitConditionalExpression(CS.Syntax.ConditionalExpressionSyntax node) + { + VB.Syntax.ArgumentSyntax[] argumentsArray = new VB.Syntax.ArgumentSyntax[] + { + VB.SyntaxFactory.SimpleArgument(VisitExpression(node.Condition)), + VB.SyntaxFactory.SimpleArgument(VisitExpression(node.WhenTrue)), + VB.SyntaxFactory.SimpleArgument(VisitExpression(node.WhenFalse)) + }; + + return VB.SyntaxFactory.InvocationExpression( + VB.SyntaxFactory.IdentifierName(VB.SyntaxFactory.Identifier("If")), + VB.SyntaxFactory.ArgumentList(SeparatedCommaList(argumentsArray))); + } + + public override SyntaxNode VisitElementAccessExpression(CS.Syntax.ElementAccessExpressionSyntax node) + => VB.SyntaxFactory.InvocationExpression( + VisitExpression(node.Expression), + Visit(node.ArgumentList)); + + public override SyntaxNode VisitParenthesizedExpression(CS.Syntax.ParenthesizedExpressionSyntax node) + => VB.SyntaxFactory.ParenthesizedExpression( + VisitExpression(node.Expression)); + + public override SyntaxNode VisitImplicitArrayCreationExpression(CS.Syntax.ImplicitArrayCreationExpressionSyntax node) + => Visit(node.Initializer); + + public override SyntaxNode VisitInitializerExpression(CS.Syntax.InitializerExpressionSyntax node) + { + if (node.Parent.IsKind(CS.SyntaxKind.AnonymousObjectCreationExpression)) + { + List fieldInitializers = new List(); + foreach (ExpressionSyntax expression in node.Expressions) + { + if (expression.IsKind(CS.SyntaxKind.SimpleAssignmentExpression)) + { + AssignmentExpressionSyntax assignment = (CS.Syntax.AssignmentExpressionSyntax)expression; + if (assignment.Left.IsKind(CS.SyntaxKind.IdentifierName)) + { + fieldInitializers.Add(VB.SyntaxFactory.NamedFieldInitializer( + VB.SyntaxFactory.IdentifierName(ConvertIdentifier((CS.Syntax.IdentifierNameSyntax)assignment.Left)), + VisitExpression(assignment.Right))); + continue; + } + } + + fieldInitializers.Add(VB.SyntaxFactory.InferredFieldInitializer(VisitExpression(expression))); + } + + return VB.SyntaxFactory.ObjectMemberInitializer(fieldInitializers.ToArray()); + } + else if (node.Parent.IsKind(CS.SyntaxKind.ObjectCreationExpression)) + { + if (node.Expressions.Count > 0 && + node.Expressions[0].IsKind(CS.SyntaxKind.SimpleAssignmentExpression)) + { + List initializers = new List(); + foreach (ExpressionSyntax e in node.Expressions) + { + if (e.IsKind(CS.SyntaxKind.SimpleAssignmentExpression)) + { + AssignmentExpressionSyntax assignment = (CS.Syntax.AssignmentExpressionSyntax)e; + if (assignment.Left.IsKind(CS.SyntaxKind.IdentifierName)) + { + initializers.Add( + VB.SyntaxFactory.NamedFieldInitializer( + Visit(assignment.Left), + VisitExpression(assignment.Right))); + continue; + } + } + + initializers.Add( + VB.SyntaxFactory.InferredFieldInitializer(VisitExpression(e))); + } + + return VB.SyntaxFactory.ObjectMemberInitializer(initializers.ToArray()); + } + else + { + return VB.SyntaxFactory.ObjectCollectionInitializer( + VB.SyntaxFactory.CollectionInitializer( + SeparatedCommaList(node.Expressions.Select(VisitExpression)))); + } + } + else + { + return VB.SyntaxFactory.CollectionInitializer( + SeparatedCommaList(node.Expressions.Select(VisitExpression))); + } + } + + public override SyntaxNode VisitForEachStatement(CS.Syntax.ForEachStatementSyntax node) + { + VB.Syntax.ForEachStatementSyntax begin = VB.SyntaxFactory.ForEachStatement( + VB.SyntaxFactory.IdentifierName(ConvertIdentifier(node.Identifier)), + VisitExpression(node.Expression)); + return VB.SyntaxFactory.ForEachBlock( + begin, + statementVisitor.VisitStatement(node.Statement), + VB.SyntaxFactory.NextStatement()); + } + + public override SyntaxNode VisitAttributeList(CS.Syntax.AttributeListSyntax node) + => VB.SyntaxFactory.AttributeList( + SeparatedCommaList(node.Attributes.Select(Visit))); + + public override SyntaxNode VisitAttribute(CS.Syntax.AttributeSyntax node) + { + AttributeListSyntax parent = (CS.Syntax.AttributeListSyntax)node.Parent; + return VB.SyntaxFactory.Attribute( + Visit(parent.Target), + VisitType(node.Name), + Visit(node.ArgumentList)); + } + + public override SyntaxNode VisitAttributeTargetSpecifier(CS.Syntax.AttributeTargetSpecifierSyntax node) + { + // todo: any other types of attribute targets (like 'return', etc.) + // should cause us to actually move the attribute to a different + // location in the VB signature. + // + // For now, we only handle assembly/module. + + switch (node.Identifier.ValueText) + { + default: + return null; + case "assembly": + case "module": + SyntaxToken modifier = VisitToken(node.Identifier); + return VB.SyntaxFactory.AttributeTarget( + modifier, + VB.SyntaxFactory.Token(VB.SyntaxKind.ColonToken)); + } + } + + public override SyntaxNode VisitAttributeArgumentList(CS.Syntax.AttributeArgumentListSyntax node) + => VB.SyntaxFactory.ArgumentList(SeparatedCommaList(node.Arguments.Select(Visit))); + + public override SyntaxNode VisitAttributeArgument(CS.Syntax.AttributeArgumentSyntax node) + { + if (node.NameEquals == null) + { + return VB.SyntaxFactory.SimpleArgument(VisitExpression(node.Expression)); + } + else + { + return VB.SyntaxFactory.SimpleArgument( + VB.SyntaxFactory.NameColonEquals( + VB.SyntaxFactory.IdentifierName(ConvertIdentifier(node.NameEquals.Name))), + VisitExpression(node.Expression)); + } + } + + public override SyntaxNode VisitPropertyDeclaration(CS.Syntax.PropertyDeclarationSyntax node) + { + SyntaxTokenList modifiers = ConvertModifiers(node.Modifiers); + if (node.AccessorList.Accessors.Count == 1) + { + if (node.AccessorList.Accessors[0].Keyword.IsKind(CS.SyntaxKind.GetKeyword)) + { + modifiers = TokenList(modifiers.Concat(VB.SyntaxFactory.Token(VB.SyntaxKind.ReadOnlyKeyword))); + } + else + { + modifiers = TokenList(modifiers.Concat(VB.SyntaxFactory.Token(VB.SyntaxKind.WriteOnlyKeyword))); + } + } + + VB.Syntax.PropertyStatementSyntax begin = VB.SyntaxFactory.PropertyStatement( + ConvertAttributes(node.AttributeLists), + modifiers, + ConvertIdentifier(node.Identifier), + null, + VB.SyntaxFactory.SimpleAsClause(VisitType(node.Type)), + null, + null); + + if (node.AccessorList.Accessors.All(a => a.Body == null)) + { + return begin; + } + + return VB.SyntaxFactory.PropertyBlock( + begin, + List(node.AccessorList.Accessors.Select(Visit))); + } + + public override SyntaxNode VisitIndexerDeclaration(CS.Syntax.IndexerDeclarationSyntax node) + { + VB.Syntax.PropertyStatementSyntax begin = VB.SyntaxFactory.PropertyStatement( + ConvertAttributes(node.AttributeLists), + ConvertModifiers(node.Modifiers), + VB.SyntaxFactory.Identifier("Item"), + Visit(node.ParameterList), + VB.SyntaxFactory.SimpleAsClause(VisitType(node.Type)), + null, + null); + + return VB.SyntaxFactory.PropertyBlock( + begin, + List(node.AccessorList.Accessors.Select(Visit))); + } + + public override SyntaxNode VisitAccessorDeclaration(CS.Syntax.AccessorDeclarationSyntax node) + { + SyntaxList attributes = ConvertAttributes(node.AttributeLists); + SyntaxTokenList modifiers = ConvertModifiers(node.Modifiers); + SyntaxList body = statementVisitor.VisitStatement(node.Body); + + switch (node.Kind()) + { + case CS.SyntaxKind.AddAccessorDeclaration: + { + VB.Syntax.AccessorStatementSyntax begin = VB.SyntaxFactory.AddHandlerAccessorStatement( + attributes, modifiers, null); + + return VB.SyntaxFactory.AddHandlerAccessorBlock( + begin, body, + VB.SyntaxFactory.EndAddHandlerStatement()); + } + + case CS.SyntaxKind.GetAccessorDeclaration: + { + VB.Syntax.AccessorStatementSyntax begin = VB.SyntaxFactory.GetAccessorStatement( + attributes, modifiers, null); + + return VB.SyntaxFactory.GetAccessorBlock( + begin, body, + VB.SyntaxFactory.EndGetStatement()); + } + + case CS.SyntaxKind.RemoveAccessorDeclaration: + { + VB.Syntax.AccessorStatementSyntax begin = VB.SyntaxFactory.RemoveHandlerAccessorStatement( + attributes, modifiers, null); + + return VB.SyntaxFactory.RemoveHandlerAccessorBlock( + begin, body, + VB.SyntaxFactory.EndRemoveHandlerStatement()); + } + + case CS.SyntaxKind.SetAccessorDeclaration: + { + VB.Syntax.AccessorStatementSyntax begin = VB.SyntaxFactory.SetAccessorStatement( + attributes, modifiers, null); + + return VB.SyntaxFactory.SetAccessorBlock( + begin, body, + VB.SyntaxFactory.EndSetStatement()); + } + + default: + throw new NotImplementedException(); + } + } + + // private static readonly Regex SpaceSlashSlashSlashRegex = + // new Regex("^(\\s*)///(.*)$", RegexOptions.Compiled | RegexOptions.Singleline); + // private static readonly Regex SpaceStarRegex = + // new Regex("^(\\s*)((/\\*\\*)|(\\*)|(\\*/))(.*)$", RegexOptions.Compiled | RegexOptions.Singleline); + // private static readonly char[] LineSeparators = { '\r', '\n', '\u0085', '\u2028', '\u2029' }; + + public override SyntaxNode VisitDocumentationCommentTrivia(CS.Syntax.DocumentationCommentTriviaSyntax node) + { + string text = node.ToFullString().Replace("///", "'''"); + VB.Syntax.CompilationUnitSyntax root = VB.SyntaxFactory.ParseSyntaxTree(text).GetRoot() as VB.Syntax.CompilationUnitSyntax; + return root.EndOfFileToken.LeadingTrivia.ElementAt(0).GetStructure(); + } + + public override SyntaxNode VisitArrayType(CS.Syntax.ArrayTypeSyntax node) + => VB.SyntaxFactory.ArrayType( + VisitType(node.ElementType), + List(node.RankSpecifiers.Select(Visit))); + + public override SyntaxNode VisitArrayRankSpecifier(CS.Syntax.ArrayRankSpecifierSyntax node) + { + // TODO: pass the right number of commas + return VB.SyntaxFactory.ArrayRankSpecifier(); + } + + public override SyntaxNode VisitArrayCreationExpression(CS.Syntax.ArrayCreationExpressionSyntax node) + { + VB.Syntax.CollectionInitializerSyntax initializer = Visit(node.Initializer); + if (initializer == null) + { + initializer = VB.SyntaxFactory.CollectionInitializer(); + } + + VB.Syntax.ArrayTypeSyntax arrayType = (VB.Syntax.ArrayTypeSyntax)VisitType(node.Type); + + return VB.SyntaxFactory.ArrayCreationExpression( + VB.SyntaxFactory.Token(VB.SyntaxKind.NewKeyword), + new SyntaxList(), + arrayType.ElementType, + null, + arrayType.RankSpecifiers, + initializer); + } + + public override SyntaxNode VisitVariableDeclaration(CS.Syntax.VariableDeclarationSyntax node) + => VB.SyntaxFactory.LocalDeclarationStatement( + VB.SyntaxFactory.TokenList(VB.SyntaxFactory.Token(VB.SyntaxKind.DimKeyword)), + SeparatedCommaList(node.Variables.Select(Visit))); + + public override SyntaxNode VisitPostfixUnaryExpression(CS.Syntax.PostfixUnaryExpressionSyntax node) + { + VB.Syntax.ExpressionSyntax operand = VisitExpression(node.Operand); + + switch (node.Kind()) + { + case CS.SyntaxKind.PostIncrementExpression: + return VB.SyntaxFactory.SimpleAssignmentStatement( + operand, + VB.SyntaxFactory.Token(VB.SyntaxKind.EqualsToken), + VB.SyntaxFactory.AddExpression(operand, + VB.SyntaxFactory.NumericLiteralExpression(VB.SyntaxFactory.IntegerLiteralToken("1", VB.Syntax.LiteralBase.Decimal, VB.Syntax.TypeCharacter.None, 1)))); + case CS.SyntaxKind.PostDecrementExpression: + return VB.SyntaxFactory.SimpleAssignmentStatement( + operand, + VB.SyntaxFactory.Token(VB.SyntaxKind.EqualsToken), + VB.SyntaxFactory.SubtractExpression(operand, + VB.SyntaxFactory.NumericLiteralExpression(VB.SyntaxFactory.IntegerLiteralToken("1", VB.Syntax.LiteralBase.Decimal, VB.Syntax.TypeCharacter.None, 1)))); + } + + throw new NotImplementedException(); + } + + public override SyntaxNode VisitOperatorDeclaration(CS.Syntax.OperatorDeclarationSyntax node) + { + SyntaxToken @operator; + switch (node.OperatorToken.Kind()) + { + case CS.SyntaxKind.AmpersandToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.AndKeyword); + break; + case CS.SyntaxKind.AmpersandAmpersandToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.AndAlsoKeyword); + break; + case CS.SyntaxKind.AsteriskToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.AsteriskToken); + break; + case CS.SyntaxKind.BarToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.OrKeyword); + break; + case CS.SyntaxKind.CaretToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.XorKeyword); + break; + case CS.SyntaxKind.MinusToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.MinusToken); + break; + case CS.SyntaxKind.MinusMinusToken: + @operator = VB.SyntaxFactory.Identifier("Decrement"); + break; + case CS.SyntaxKind.EqualsEqualsToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.EqualsToken); + break; + case CS.SyntaxKind.FalseKeyword: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.IsFalseKeyword); + break; + case CS.SyntaxKind.ExclamationToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.NotKeyword); + break; + case CS.SyntaxKind.ExclamationEqualsToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.LessThanGreaterThanToken); + break; + case CS.SyntaxKind.GreaterThanToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.GreaterThanToken); + break; + case CS.SyntaxKind.GreaterThanGreaterThanToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.GreaterThanGreaterThanToken); + break; + case CS.SyntaxKind.GreaterThanEqualsToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.GreaterThanEqualsToken); + break; + case CS.SyntaxKind.LessThanToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.LessThanToken); + break; + case CS.SyntaxKind.LessThanEqualsToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.LessThanEqualsToken); + break; + case CS.SyntaxKind.LessThanLessThanToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.LessThanLessThanToken); + break; + case CS.SyntaxKind.PercentToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.ModKeyword); + break; + case CS.SyntaxKind.PlusToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.PlusToken); + break; + case CS.SyntaxKind.PlusPlusToken: + @operator = VB.SyntaxFactory.Identifier("Increment"); + break; + case CS.SyntaxKind.SlashToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.SlashToken); + break; + case CS.SyntaxKind.TildeToken: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.NotKeyword); + break; + case CS.SyntaxKind.TrueKeyword: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.IsTrueKeyword); + break; + case CS.SyntaxKind.ExplicitKeyword: + case CS.SyntaxKind.ImplicitKeyword: + case CS.SyntaxKind.None: + @operator = VB.SyntaxFactory.Token(VB.SyntaxKind.EmptyToken, node.OperatorToken.ToString()); + break; + default: + throw new NotImplementedException(); + } + SplitAttributes(node.AttributeLists.ToList(), out SyntaxList returnAttributes, out SyntaxList remainingAttributes); + + VB.Syntax.OperatorStatementSyntax begin = VB.SyntaxFactory.OperatorStatement( + remainingAttributes, + ConvertModifiers(node.Modifiers), + @operator, + Visit(node.ParameterList), + VB.SyntaxFactory.SimpleAsClause(returnAttributes, VisitType(node.ReturnType))); + + if (node.Body == null) + { + return begin; + } + + return VB.SyntaxFactory.OperatorBlock( + begin, + statementVisitor.VisitStatement(node.Body), + VB.SyntaxFactory.EndOperatorStatement()); + } + + public override SyntaxNode VisitPrefixUnaryExpression(CS.Syntax.PrefixUnaryExpressionSyntax node) + { + switch (node.Kind()) + { + case CS.SyntaxKind.AddressOfExpression: + return VB.SyntaxFactory.AddressOfExpression(VisitExpression(node.Operand)); + case CS.SyntaxKind.BitwiseNotExpression: + case CS.SyntaxKind.LogicalNotExpression: + return VB.SyntaxFactory.NotExpression(VisitExpression(node.Operand)); + case CS.SyntaxKind.UnaryMinusExpression: + return VB.SyntaxFactory.UnaryMinusExpression(VisitExpression(node.Operand)); + case CS.SyntaxKind.UnaryPlusExpression: + return VB.SyntaxFactory.UnaryPlusExpression(VisitExpression(node.Operand)); + case CS.SyntaxKind.PointerIndirectionExpression: + return Visit(node.Operand); + case CS.SyntaxKind.PreDecrementExpression: + { + VB.Syntax.ExpressionSyntax operand = VisitExpression(node.Operand); + return VB.SyntaxFactory.SimpleAssignmentStatement( + operand, + VB.SyntaxFactory.Token(VB.SyntaxKind.EqualsToken), + VB.SyntaxFactory.SubtractExpression(operand, VB.SyntaxFactory.NumericLiteralExpression( + VB.SyntaxFactory.IntegerLiteralToken("1", VB.Syntax.LiteralBase.Decimal, VB.Syntax.TypeCharacter.None, 1)))); + } + + case CS.SyntaxKind.PreIncrementExpression: + { + VB.Syntax.ExpressionSyntax operand = VisitExpression(node.Operand); + return VB.SyntaxFactory.SimpleAssignmentStatement( + operand, + VB.SyntaxFactory.Token(VB.SyntaxKind.EqualsToken), + VB.SyntaxFactory.AddExpression(operand, VB.SyntaxFactory.NumericLiteralExpression( + VB.SyntaxFactory.IntegerLiteralToken("1", VB.Syntax.LiteralBase.Decimal, VB.Syntax.TypeCharacter.None, 1)))); + } + + default: + throw new NotImplementedException(); + } + } + + public override SyntaxNode VisitAwaitExpression(CS.Syntax.AwaitExpressionSyntax node) + => VB.SyntaxFactory.AwaitExpression(VisitExpression(node.Expression)); + + public override SyntaxNode VisitDefaultExpression(CS.Syntax.DefaultExpressionSyntax node) + => VB.SyntaxFactory.NothingLiteralExpression(VB.SyntaxFactory.Token(VB.SyntaxKind.NothingKeyword)); + + public override SyntaxNode VisitTypeOfExpression(CS.Syntax.TypeOfExpressionSyntax node) + => VB.SyntaxFactory.GetTypeExpression(VisitType(node.Type)); + + public override SyntaxNode VisitCheckedExpression(CS.Syntax.CheckedExpressionSyntax node) + { + string functionName; + switch (node.Kind()) + { + case CS.SyntaxKind.CheckedExpression: + functionName = "Checked"; + break; + case CS.SyntaxKind.UncheckedExpression: + functionName = "Unchecked"; + break; + default: + throw new NotImplementedException(); + } + + return VB.SyntaxFactory.InvocationExpression( + VB.SyntaxFactory.StringLiteralExpression(VB.SyntaxFactory.StringLiteralToken(functionName, functionName)), + VB.SyntaxFactory.ArgumentList( + SeparatedList( + VB.SyntaxFactory.SimpleArgument(VisitExpression(node.Expression))))); + } + + public override SyntaxNode VisitMakeRefExpression(CS.Syntax.MakeRefExpressionSyntax node) + { + const string FunctionName = "MakeRef"; + return VB.SyntaxFactory.InvocationExpression( + VB.SyntaxFactory.StringLiteralExpression(VB.SyntaxFactory.StringLiteralToken(FunctionName, FunctionName)), + VB.SyntaxFactory.ArgumentList( + SeparatedList( + VB.SyntaxFactory.SimpleArgument(VisitExpression(node.Expression))))); + } + + public override SyntaxNode VisitRefTypeExpression(CS.Syntax.RefTypeExpressionSyntax node) + { + const string FunctionName = "RefType"; + return VB.SyntaxFactory.InvocationExpression( + VB.SyntaxFactory.StringLiteralExpression(VB.SyntaxFactory.StringLiteralToken(FunctionName, FunctionName)), + VB.SyntaxFactory.ArgumentList( + SeparatedList( + VB.SyntaxFactory.SimpleArgument(VisitExpression(node.Expression))))); + } + + public override SyntaxNode VisitRefValueExpression(CS.Syntax.RefValueExpressionSyntax node) + { + const string FunctionName = "RefValue"; + return VB.SyntaxFactory.InvocationExpression( + VB.SyntaxFactory.StringLiteralExpression(VB.SyntaxFactory.StringLiteralToken(FunctionName, FunctionName)), + VB.SyntaxFactory.ArgumentList( + SeparatedList( + VB.SyntaxFactory.SimpleArgument(VisitExpression(node.Expression))))); + } + + public override SyntaxNode VisitSizeOfExpression(CS.Syntax.SizeOfExpressionSyntax node) + { + const string FunctionName = "SizeOf"; + return VB.SyntaxFactory.InvocationExpression( + VB.SyntaxFactory.StringLiteralExpression(VB.SyntaxFactory.StringLiteralToken(FunctionName, FunctionName)), + VB.SyntaxFactory.ArgumentList( + SeparatedList( + VB.SyntaxFactory.SimpleArgument(VisitType(node.Type))))); + } + + public override SyntaxNode VisitBadDirectiveTrivia(CS.Syntax.BadDirectiveTriviaSyntax node) + { + SyntaxTrivia comment = VB.SyntaxFactory.CommentTrivia(CreateCouldNotBeConvertedComment(node.ToFullString(), typeof(VB.Syntax.DirectiveTriviaSyntax))); + return VB.SyntaxFactory.BadDirectiveTrivia( + VB.SyntaxFactory.Token(VB.SyntaxKind.HashToken).WithTrailingTrivia(comment)); + } + + public override SyntaxNode VisitWarningDirectiveTrivia(CS.Syntax.WarningDirectiveTriviaSyntax node) => CreateBadDirective(node, this); + + public override SyntaxNode VisitErrorDirectiveTrivia(CS.Syntax.ErrorDirectiveTriviaSyntax node) => CreateBadDirective(node, this); + + public override SyntaxNode VisitRegionDirectiveTrivia(CS.Syntax.RegionDirectiveTriviaSyntax node) + => VB.SyntaxFactory.RegionDirectiveTrivia( + VB.SyntaxFactory.Token(VB.SyntaxKind.HashToken), + VB.SyntaxFactory.Token(VB.SyntaxKind.RegionKeyword), + VB.SyntaxFactory.StringLiteralToken(node.EndOfDirectiveToken.ToString(), node.EndOfDirectiveToken.ToString())); + + public override SyntaxNode VisitEndRegionDirectiveTrivia(CS.Syntax.EndRegionDirectiveTriviaSyntax node) + => VB.SyntaxFactory.EndRegionDirectiveTrivia( + VB.SyntaxFactory.Token(VB.SyntaxKind.HashToken), + VB.SyntaxFactory.Token(VB.SyntaxKind.EndKeyword), + VB.SyntaxFactory.Token(VB.SyntaxKind.RegionKeyword)); + + public override SyntaxNode VisitEndIfDirectiveTrivia(CS.Syntax.EndIfDirectiveTriviaSyntax node) + => VB.SyntaxFactory.EndIfDirectiveTrivia( + VB.SyntaxFactory.Token(VB.SyntaxKind.HashToken), + VB.SyntaxFactory.Token(VB.SyntaxKind.EndKeyword), + VB.SyntaxFactory.Token(VB.SyntaxKind.IfKeyword)); + + public override SyntaxNode VisitElseDirectiveTrivia(CS.Syntax.ElseDirectiveTriviaSyntax node) + => VB.SyntaxFactory.ElseDirectiveTrivia( + VB.SyntaxFactory.Token(VB.SyntaxKind.HashToken), + VB.SyntaxFactory.Token(VB.SyntaxKind.ElseKeyword)); + + public override SyntaxNode VisitIfDirectiveTrivia(CS.Syntax.IfDirectiveTriviaSyntax node) + => VB.SyntaxFactory.IfDirectiveTrivia( + VB.SyntaxFactory.Token(VB.SyntaxKind.HashToken), + new SyntaxToken(), + VB.SyntaxFactory.Token(VB.SyntaxKind.IfKeyword), + VisitExpression(node.Condition), + new SyntaxToken()); + + public override SyntaxNode VisitElifDirectiveTrivia(CS.Syntax.ElifDirectiveTriviaSyntax node) + => VB.SyntaxFactory.ElseIfDirectiveTrivia( + VB.SyntaxFactory.Token(VB.SyntaxKind.HashToken), + new SyntaxToken(), + VB.SyntaxFactory.Token(VB.SyntaxKind.ElseIfKeyword), + VisitExpression(node.Condition), + new SyntaxToken()); + + public override SyntaxNode VisitEnumMemberDeclaration(CS.Syntax.EnumMemberDeclarationSyntax node) + { + VB.Syntax.ExpressionSyntax expression = node.EqualsValue == null ? null : VisitExpression(node.EqualsValue.Value); + VB.Syntax.EqualsValueSyntax initializer = expression == null ? null : VB.SyntaxFactory.EqualsValue(expression); + return VB.SyntaxFactory.EnumMemberDeclaration( + ConvertAttributes(node.AttributeLists), + ConvertIdentifier(node.Identifier), + initializer); + } + + public override SyntaxNode VisitNullableType(CS.Syntax.NullableTypeSyntax node) + => VB.SyntaxFactory.NullableType(VisitType(node.ElementType)); + + public override SyntaxNode VisitAnonymousMethodExpression(CS.Syntax.AnonymousMethodExpressionSyntax node) + { + VB.Syntax.LambdaHeaderSyntax begin = VB.SyntaxFactory.FunctionLambdaHeader( + new SyntaxList(), + new SyntaxTokenList(), + Visit(node.ParameterList), + null); + return VB.SyntaxFactory.MultiLineFunctionLambdaExpression( + begin, + statementVisitor.VisitStatement(node.Block), + VB.SyntaxFactory.EndFunctionStatement()); + } + + public override SyntaxNode VisitQueryExpression(CS.Syntax.QueryExpressionSyntax node) + { + IEnumerable newClauses = + Enumerable.Repeat(Visit(node.FromClause), 1) + .Concat(node.Body.Clauses.Select(Visit)) + .Concat(Visit(node.Body.SelectOrGroup)); + return VB.SyntaxFactory.QueryExpression(List(newClauses)); + } + + public override SyntaxNode VisitSelectClause(CS.Syntax.SelectClauseSyntax node) + => VB.SyntaxFactory.SelectClause( + VB.SyntaxFactory.ExpressionRangeVariable(VisitExpression(node.Expression))); + + public override SyntaxNode VisitFromClause(CS.Syntax.FromClauseSyntax node) + { + VB.Syntax.CollectionRangeVariableSyntax initializer = VB.SyntaxFactory.CollectionRangeVariable( + VB.SyntaxFactory.ModifiedIdentifier(ConvertIdentifier(node.Identifier)), + node.Type == null ? null : VB.SyntaxFactory.SimpleAsClause(VisitType(node.Type)), + VisitExpression(node.Expression)); + + return VB.SyntaxFactory.FromClause(initializer); + } + + public override SyntaxNode VisitOrderByClause(CS.Syntax.OrderByClauseSyntax node) + => VB.SyntaxFactory.OrderByClause( + node.Orderings.Select(Visit).ToArray()); + + public override SyntaxNode VisitOrdering(CS.Syntax.OrderingSyntax node) + { + if (node.AscendingOrDescendingKeyword.IsKind(CS.SyntaxKind.None) || + node.AscendingOrDescendingKeyword.IsKind(CS.SyntaxKind.AscendingKeyword)) + { + return VB.SyntaxFactory.AscendingOrdering(VisitExpression(node.Expression)); + } + else + { + return VB.SyntaxFactory.DescendingOrdering(VisitExpression(node.Expression)); + } + } + + public override SyntaxNode VisitWhereClause(CS.Syntax.WhereClauseSyntax node) + => VB.SyntaxFactory.WhereClause(VisitExpression(node.Condition)); + + public override SyntaxNode VisitJoinClause(CS.Syntax.JoinClauseSyntax node) + { + if (node.Into == null) + { + return VB.SyntaxFactory.SimpleJoinClause( + SeparatedList(VB.SyntaxFactory.CollectionRangeVariable( + VB.SyntaxFactory.ModifiedIdentifier(ConvertIdentifier(node.Identifier)), + node.Type == null ? null : VB.SyntaxFactory.SimpleAsClause(VisitType(node.Type)), + VisitExpression(node.InExpression))), + new SyntaxList(), + SeparatedList(VB.SyntaxFactory.JoinCondition( + VisitExpression(node.LeftExpression), + VisitExpression(node.RightExpression)))); + } + else + { + return VB.SyntaxFactory.GroupJoinClause( + SeparatedList(VB.SyntaxFactory.CollectionRangeVariable( + VB.SyntaxFactory.ModifiedIdentifier(ConvertIdentifier(node.Identifier)), + node.Type == null ? null : VB.SyntaxFactory.SimpleAsClause(VisitType(node.Type)), + VisitExpression(node.InExpression))), + new SyntaxList(), + SeparatedList(VB.SyntaxFactory.JoinCondition( + VisitExpression(node.LeftExpression), + VisitExpression(node.RightExpression))), + SeparatedList(VB.SyntaxFactory.AggregationRangeVariable( + VB.SyntaxFactory.VariableNameEquals(VB.SyntaxFactory.ModifiedIdentifier(ConvertIdentifier(node.Into.Identifier))), + VB.SyntaxFactory.GroupAggregation()))); + } + } + + public override SyntaxNode VisitGroupClause(CS.Syntax.GroupClauseSyntax node) + { + VB.Syntax.ExpressionRangeVariableSyntax groupExpression = VB.SyntaxFactory.ExpressionRangeVariable( + null, VisitExpression(node.GroupExpression)); + VB.Syntax.ExpressionRangeVariableSyntax byExpression = VB.SyntaxFactory.ExpressionRangeVariable( + null, VisitExpression(node.ByExpression)); + QueryExpressionSyntax query = (CS.Syntax.QueryExpressionSyntax)node.Parent; + VB.Syntax.AggregationRangeVariableSyntax rangeVariable; + if (query.Body.Continuation == null) + { + rangeVariable = VB.SyntaxFactory.AggregationRangeVariable(VB.SyntaxFactory.GroupAggregation()); + } + else + { + rangeVariable = VB.SyntaxFactory.AggregationRangeVariable( + VB.SyntaxFactory.VariableNameEquals(VB.SyntaxFactory.ModifiedIdentifier(ConvertIdentifier(query.Body.Continuation.Identifier))), + VB.SyntaxFactory.GroupAggregation(VB.SyntaxFactory.Token(VB.SyntaxKind.GroupKeyword))); + } + + return VB.SyntaxFactory.GroupByClause( + SeparatedList(groupExpression), + SeparatedList(byExpression), + SeparatedList(rangeVariable)); + } + + public override SyntaxNode VisitLetClause(CS.Syntax.LetClauseSyntax node) + => VB.SyntaxFactory.LetClause( + VB.SyntaxFactory.ExpressionRangeVariable( + VB.SyntaxFactory.VariableNameEquals(VB.SyntaxFactory.ModifiedIdentifier(ConvertIdentifier(node.Identifier))), + VisitExpression(node.Expression))); + + public override SyntaxNode VisitAnonymousObjectCreationExpression(CS.Syntax.AnonymousObjectCreationExpressionSyntax node) + => VB.SyntaxFactory.AnonymousObjectCreationExpression( + VB.SyntaxFactory.ObjectMemberInitializer( + node.Initializers.Select(Visit).ToArray())); + + public override SyntaxNode VisitAnonymousObjectMemberDeclarator(CS.Syntax.AnonymousObjectMemberDeclaratorSyntax node) + => node.NameEquals == null + ? VB.SyntaxFactory.InferredFieldInitializer(VisitExpression(node.Expression)) + : (VB.Syntax.FieldInitializerSyntax)VB.SyntaxFactory.NamedFieldInitializer( + VB.SyntaxFactory.IdentifierName(ConvertIdentifier(node.NameEquals.Name)), + VisitExpression(node.Expression)); + + public override SyntaxNode VisitDefineDirectiveTrivia(CS.Syntax.DefineDirectiveTriviaSyntax node) + => CreateBadDirective(node, this); + + public override SyntaxNode VisitUndefDirectiveTrivia(CS.Syntax.UndefDirectiveTriviaSyntax node) + => CreateBadDirective(node, this); + + public override SyntaxNode VisitPragmaWarningDirectiveTrivia(CS.Syntax.PragmaWarningDirectiveTriviaSyntax node) + => CreateBadDirective(node, this); + + public override SyntaxNode VisitPragmaChecksumDirectiveTrivia(CS.Syntax.PragmaChecksumDirectiveTriviaSyntax node) + => CreateBadDirective(node, this); + + public override SyntaxNode VisitLineDirectiveTrivia(CS.Syntax.LineDirectiveTriviaSyntax node) + => CreateBadDirective(node, this); + + public override SyntaxNode VisitFinallyClause(CS.Syntax.FinallyClauseSyntax node) + => VB.SyntaxFactory.FinallyBlock( + statementVisitor.VisitStatement(node.Block)); + + public override SyntaxNode VisitCatchClause(CS.Syntax.CatchClauseSyntax node) + { + VB.Syntax.CatchStatementSyntax statement; + if (node.Declaration == null) + { + statement = VB.SyntaxFactory.CatchStatement(); + } + else if (node.Declaration.Identifier.IsKind(CS.SyntaxKind.None)) + { + statement = VB.SyntaxFactory.CatchStatement( + null, + VB.SyntaxFactory.SimpleAsClause(VisitType(node.Declaration.Type)), + null); + } + else + { + statement = VB.SyntaxFactory.CatchStatement( + VB.SyntaxFactory.IdentifierName(ConvertIdentifier(node.Declaration.Identifier)), + VB.SyntaxFactory.SimpleAsClause(VisitType(node.Declaration.Type)), + null); + } + + return VB.SyntaxFactory.CatchBlock( + statement, + statementVisitor.VisitStatement(node.Block)); + } + + public override SyntaxNode VisitConversionOperatorDeclaration(CS.Syntax.ConversionOperatorDeclarationSyntax node) + { + SyntaxToken direction = node.Modifiers.Any(t => t.IsKind(CS.SyntaxKind.ImplicitKeyword)) + ? VB.SyntaxFactory.Token(VB.SyntaxKind.WideningKeyword) + : VB.SyntaxFactory.Token(VB.SyntaxKind.NarrowingKeyword); + + VB.Syntax.OperatorStatementSyntax begin = VB.SyntaxFactory.OperatorStatement( + ConvertAttributes(node.AttributeLists), + TokenList(ConvertModifiers(node.Modifiers).Concat(direction)), + VB.SyntaxFactory.Token(VB.SyntaxKind.CTypeKeyword), + Visit(node.ParameterList), + VB.SyntaxFactory.SimpleAsClause(VisitType(node.Type))); + + if (node.Body == null) + { + return begin; + } + + return VB.SyntaxFactory.OperatorBlock( + begin, + statementVisitor.VisitStatement(node.Body), + VB.SyntaxFactory.EndOperatorStatement()); + } + + public override SyntaxNode VisitPointerType(CS.Syntax.PointerTypeSyntax node) + // just ignore the pointer part + => Visit(node.ElementType); + + public override SyntaxNode VisitDestructorDeclaration(CS.Syntax.DestructorDeclarationSyntax node) + { + VB.Syntax.MethodStatementSyntax begin = VB.SyntaxFactory.SubStatement( + new SyntaxList(), + new SyntaxTokenList(), + VB.SyntaxFactory.Identifier("Finalize"), + null, + VB.SyntaxFactory.ParameterList(), + null, null, null); + + if (node.Body == null) + { + return begin; + } + + return VB.SyntaxFactory.SubBlock( + begin, + statementVisitor.VisitStatement(node.Body), + VB.SyntaxFactory.EndSubStatement()); + } + + public override SyntaxNode VisitDelegateDeclaration(CS.Syntax.DelegateDeclarationSyntax node) + { + SyntaxToken identifier = ConvertIdentifier(node.Identifier); + VB.Syntax.TypeParameterListSyntax typeParameters = Visit(node.TypeParameterList); + + if (node.ReturnType.IsKind(CS.SyntaxKind.PredefinedType) && + ((CS.Syntax.PredefinedTypeSyntax)node.ReturnType).Keyword.IsKind(CS.SyntaxKind.VoidKeyword)) + { + return VB.SyntaxFactory.DelegateSubStatement( + ConvertAttributes(node.AttributeLists), + ConvertModifiers(node.Modifiers), + identifier, + typeParameters, + Visit(node.ParameterList), + null); + } + else + { + return VB.SyntaxFactory.DelegateFunctionStatement( + ConvertAttributes(node.AttributeLists), + ConvertModifiers(node.Modifiers), + identifier, + typeParameters, + Visit(node.ParameterList), + VB.SyntaxFactory.SimpleAsClause(VisitType(node.ReturnType))); + } + } + + public override SyntaxNode VisitEventFieldDeclaration(CS.Syntax.EventFieldDeclarationSyntax node) + => VB.SyntaxFactory.EventStatement( + ConvertAttributes(node.AttributeLists), + ConvertModifiers(node.Modifiers), + ConvertIdentifier(node.Declaration.Variables[0].Identifier), + null, + VB.SyntaxFactory.SimpleAsClause(VisitType(node.Declaration.Type)), + null); + + public override SyntaxNode VisitEventDeclaration(CS.Syntax.EventDeclarationSyntax node) + { + SyntaxToken identifier = ConvertIdentifier(node.Identifier); + + VB.Syntax.EventStatementSyntax begin = VB.SyntaxFactory.EventStatement( + ConvertAttributes(node.AttributeLists), + ConvertModifiers(node.Modifiers), + identifier, + null, + VB.SyntaxFactory.SimpleAsClause(VisitType(node.Type)), + null); + + return VB.SyntaxFactory.EventBlock( + begin, + List(node.AccessorList.Accessors.Select(Visit)), + VB.SyntaxFactory.EndEventStatement()); + } + + public override SyntaxNode VisitStackAllocArrayCreationExpression(CS.Syntax.StackAllocArrayCreationExpressionSyntax node) + { + string error = CreateCouldNotBeConvertedString(node.ToFullString(), typeof(SyntaxNode)); + return VB.SyntaxFactory.StringLiteralExpression( + VB.SyntaxFactory.StringLiteralToken(error, error)); + } + + public override SyntaxNode VisitIncompleteMember(CS.Syntax.IncompleteMemberSyntax node) + => VB.SyntaxFactory.FieldDeclaration( + ConvertAttributes(node.AttributeLists), + ConvertModifiers(node.Modifiers), + SeparatedList( + VB.SyntaxFactory.VariableDeclarator( + SeparatedList(VB.SyntaxFactory.ModifiedIdentifier(VB.SyntaxFactory.Identifier("IncompleteMember"))), + VB.SyntaxFactory.SimpleAsClause(VisitType(node.Type)), null))); + + public override SyntaxNode VisitExternAliasDirective(CS.Syntax.ExternAliasDirectiveSyntax node) + { + IEnumerable leadingTrivia = node.GetFirstToken(includeSkipped: true).LeadingTrivia.SelectMany(VisitTrivia); + IEnumerable trailingTrivia = node.GetLastToken(includeSkipped: true).TrailingTrivia.SelectMany(VisitTrivia); + + SyntaxTrivia comment = VB.SyntaxFactory.CommentTrivia( + CreateCouldNotBeConvertedComment(node.ToString(), typeof(VB.Syntax.ImportsStatementSyntax))); + leadingTrivia = leadingTrivia.Concat(comment); + + return VB.SyntaxFactory.ImportsStatement( + VB.SyntaxFactory.Token(TriviaList(leadingTrivia), VB.SyntaxKind.ImportsKeyword, TriviaList(trailingTrivia), string.Empty), + new SeparatedSyntaxList()); + } + + public override SyntaxNode VisitConstructorInitializer(CS.Syntax.ConstructorInitializerSyntax node) + { + VB.Syntax.InstanceExpressionSyntax expr = null; + if (node.IsKind(CS.SyntaxKind.BaseConstructorInitializer)) + { + expr = VB.SyntaxFactory.MyBaseExpression(); + } + else + { + expr = VB.SyntaxFactory.MyClassExpression(); + } + + VB.Syntax.InvocationExpressionSyntax invocation = VB.SyntaxFactory.InvocationExpression( + VB.SyntaxFactory.SimpleMemberAccessExpression( + expr, + VB.SyntaxFactory.Token(VB.SyntaxKind.DotToken), + VB.SyntaxFactory.IdentifierName(VB.SyntaxFactory.Identifier("New"))), + Visit(node.ArgumentList)); + + return VB.SyntaxFactory.ExpressionStatement(expression: invocation); + } + + public override SyntaxNode DefaultVisit(SyntaxNode node) + { + // If you hit this, it means there was some sort of CS construct + // that we haven't written a conversion routine for. Simply add + // it above and rerun. + throw new NotImplementedException(); + } + } + } +} diff --git a/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Converting/Converter.StatementVisitor.ForStatement.cs b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Converting/Converter.StatementVisitor.ForStatement.cs new file mode 100644 index 000000000..a17974e08 --- /dev/null +++ b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Converting/Converter.StatementVisitor.ForStatement.cs @@ -0,0 +1,273 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using CS = Microsoft.CodeAnalysis.CSharp; +using VB = Microsoft.CodeAnalysis.VisualBasic; + +namespace CSharpToVisualBasicConverter +{ + public partial class Converter + { + private partial class StatementVisitor + { + public override SyntaxList VisitForStatement(CS.Syntax.ForStatementSyntax node) + { + // VB doesn't have a For statement that directly maps to C#'s. However, some C# for + // statements will map to a VB for statement. Check for those common cases and + // translate those. + return IsSimpleForStatement(node) + ? VisitSimpleForStatement(node) + : VisitComplexForStatement(node); + } + + private SyntaxList VisitSimpleForStatement(CS.Syntax.ForStatementSyntax node) + { + VB.Syntax.ForStatementSyntax forStatement = CreateForStatement(node); + IEnumerable statements = VisitStatementEnumerable(node.Statement); + + VB.Syntax.ForBlockSyntax forBlock = VB.SyntaxFactory.ForBlock( + forStatement, + List(statements), + VB.SyntaxFactory.NextStatement()); + + return List(forBlock); + } + + private VB.Syntax.ForStatementSyntax CreateForStatement(CS.Syntax.ForStatementSyntax node) + { + string variableName = node.Declaration.Variables[0].Identifier.ValueText; + VB.Syntax.ForStepClauseSyntax stepClause = CreateForStepClause(node); + VB.Syntax.ExpressionSyntax toValue = CreateForToValue(node); + return VB.SyntaxFactory.ForStatement( + controlVariable: VB.SyntaxFactory.IdentifierName(variableName), + fromValue: nodeVisitor.VisitExpression(node.Declaration.Variables[0].Initializer.Value), + toValue: toValue, + stepClause: stepClause); + } + + private VB.Syntax.ExpressionSyntax CreateForToValue(CS.Syntax.ForStatementSyntax node) + { + VB.Syntax.ExpressionSyntax expression = nodeVisitor.VisitExpression(((CS.Syntax.BinaryExpressionSyntax)node.Condition).Right); + + if (!node.Condition.IsKind(CS.SyntaxKind.LessThanOrEqualExpression) && + !node.Condition.IsKind(CS.SyntaxKind.GreaterThanOrEqualExpression)) + { + if (node.Condition.IsKind(CS.SyntaxKind.LessThanExpression)) + { + return VB.SyntaxFactory.SubtractExpression( + expression, CreateOneExpression()); + } + + if (node.Condition.IsKind(CS.SyntaxKind.GreaterThanExpression)) + { + return VB.SyntaxFactory.AddExpression( + expression, CreateOneExpression()); + } + } + + return expression; + } + + private VB.Syntax.ForStepClauseSyntax CreateForStepClause(CS.Syntax.ForStatementSyntax node) + { + ExpressionSyntax incrementor = node.Incrementors[0]; + if (!incrementor.IsKind(CS.SyntaxKind.PreIncrementExpression) && + !incrementor.IsKind(CS.SyntaxKind.PostIncrementExpression)) + { + if (incrementor.IsKind(CS.SyntaxKind.PreDecrementExpression) || + incrementor.IsKind(CS.SyntaxKind.PostDecrementExpression)) + { + return VB.SyntaxFactory.ForStepClause( + VB.SyntaxFactory.UnaryMinusExpression(CreateOneExpression())); + } + + if (incrementor.IsKind(CS.SyntaxKind.AddAssignmentExpression)) + { + return VB.SyntaxFactory.ForStepClause(nodeVisitor.VisitExpression(((CS.Syntax.AssignmentExpressionSyntax)incrementor).Right)); + } + + if (incrementor.IsKind(CS.SyntaxKind.SubtractAssignmentExpression)) + { + return VB.SyntaxFactory.ForStepClause(VB.SyntaxFactory.UnaryMinusExpression( + nodeVisitor.VisitExpression(((CS.Syntax.AssignmentExpressionSyntax)incrementor).Right))); + } + } + + return null; + } + + private static VB.Syntax.LiteralExpressionSyntax CreateOneExpression() + { + return VB.SyntaxFactory.NumericLiteralExpression(VB.SyntaxFactory.IntegerLiteralToken("1", VB.Syntax.LiteralBase.Decimal, VB.Syntax.TypeCharacter.None, 1)); + } + + private bool IsSimpleForStatement(CS.Syntax.ForStatementSyntax node) + { + // Has to look like one of the following: +#if false + for (Declaration; Condition; Incrementor) + + Declaration must be one of: + var name = v1 + primitive_type name = v1 + + Condition must be one of: + name < v2 + name <= v2 + name > v2 + name >= v2 + + Incrementor must be one of: + name++; + name--; + name += v3; + name -= v3; +#endif + if (node.Declaration == null || + node.Declaration.Variables.Count != 1) + { + return false; + } + + string variableName = node.Declaration.Variables[0].Identifier.ValueText; + + return + IsSimpleForDeclaration(node) && + IsSimpleForCondition(node, variableName) && + IsSimpleForIncrementor(node, variableName); + } + + private bool IsSimpleForDeclaration(CS.Syntax.ForStatementSyntax node) + { +#if false + Declaration must be one of: + var name = v1 + primitive_type name = v1 +#endif + + if (node.Declaration != null && + node.Declaration.Variables.Count == 1 && + node.Declaration.Variables[0].Initializer != null) + { + if (node.Declaration.Type.IsVar || node.Declaration.Type.IsKind(CS.SyntaxKind.PredefinedType)) + { + return true; + } + } + + return false; + } + + private bool IsSimpleForCondition(CS.Syntax.ForStatementSyntax node, string variableName) + { +#if false + Condition must be one of: + name < v2 + name <= v2 + name > v2 + name >= v2 +#endif + if (node.Condition != null) + { + if (node.Condition.IsKind(CS.SyntaxKind.LessThanExpression) || + node.Condition.IsKind(CS.SyntaxKind.LessThanOrEqualExpression) || + node.Condition.IsKind(CS.SyntaxKind.GreaterThanExpression) || + node.Condition.IsKind(CS.SyntaxKind.GreaterThanOrEqualExpression)) + { + BinaryExpressionSyntax binaryExpression = (CS.Syntax.BinaryExpressionSyntax)node.Condition; + return binaryExpression.Left is CS.Syntax.IdentifierNameSyntax identifierName && + identifierName.Identifier.ValueText == variableName; + } + } + + return false; + } + + private bool IsSimpleForIncrementor(CS.Syntax.ForStatementSyntax node, string variableName) + { +#if false + name++; + name--; + ++name; + --name; + name += v3; + name -= v3; +#endif + if (node.Incrementors.Count == 1) + { + ExpressionSyntax incrementor = node.Incrementors[0]; + if (incrementor.IsKind(CS.SyntaxKind.PostIncrementExpression) || + incrementor.IsKind(CS.SyntaxKind.PostDecrementExpression)) + { + return ((CS.Syntax.PostfixUnaryExpressionSyntax)incrementor).Operand is CS.Syntax.IdentifierNameSyntax identifierName && + identifierName.Identifier.ValueText == variableName; + } + + if (incrementor.IsKind(CS.SyntaxKind.PreIncrementExpression) || + incrementor.IsKind(CS.SyntaxKind.PreDecrementExpression)) + { + return ((CS.Syntax.PrefixUnaryExpressionSyntax)incrementor).Operand is CS.Syntax.IdentifierNameSyntax identifierName && + identifierName.Identifier.ValueText == variableName; + } + + if (incrementor.IsKind(CS.SyntaxKind.AddAssignmentExpression) || + incrementor.IsKind(CS.SyntaxKind.SubtractAssignmentExpression)) + { + AssignmentExpressionSyntax binaryExpression = (CS.Syntax.AssignmentExpressionSyntax)incrementor; + return binaryExpression.Left is CS.Syntax.IdentifierNameSyntax identifierName && + identifierName.Identifier.ValueText == variableName; + } + } + + return false; + } + + private SyntaxList VisitComplexForStatement(CS.Syntax.ForStatementSyntax node) + { + // VB doesn't have a for loop. So convert: + // for (declarations; condition; incrementors) body into: + // + // declarations + // while (condition) { + // body; + // incrementors; + // } + + VB.Syntax.WhileStatementSyntax begin; + if (node.Condition == null) + { + begin = VB.SyntaxFactory.WhileStatement( + condition: VB.SyntaxFactory.TrueLiteralExpression(VB.SyntaxFactory.Token(VB.SyntaxKind.TrueKeyword))); + } + else + { + begin = VB.SyntaxFactory.WhileStatement( + condition: nodeVisitor.VisitExpression(node.Condition)); + } + + SyntaxList initialBlock = Visit(node.Statement); + + List whileStatements = initialBlock.Concat( + node.Incrementors.Select(nodeVisitor.VisitStatement)).ToList(); + SyntaxList whileBody = List(whileStatements); + + VB.Syntax.WhileBlockSyntax whileBlock = VB.SyntaxFactory.WhileBlock( + begin, + whileBody); + + List statements = new List(); + if (node.Declaration != null) + { + statements.Add(nodeVisitor.Visit(node.Declaration)); + } + + statements.Add(whileBlock); + + return List(statements); + } + } + } +} diff --git a/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Converting/Converter.StatementVisitor.cs b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Converting/Converter.StatementVisitor.cs new file mode 100644 index 000000000..6f246173b --- /dev/null +++ b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Converting/Converter.StatementVisitor.cs @@ -0,0 +1,366 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using CSharpToVisualBasicConverter.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using CS = Microsoft.CodeAnalysis.CSharp; +using VB = Microsoft.CodeAnalysis.VisualBasic; + +namespace CSharpToVisualBasicConverter +{ + public partial class Converter + { + private partial class StatementVisitor : CS.CSharpSyntaxVisitor> + { + private readonly NodeVisitor nodeVisitor; + private readonly SourceText text; + + public StatementVisitor(NodeVisitor nodeVisitor, SourceText text) + { + this.nodeVisitor = nodeVisitor; + this.text = text; + } + + public IEnumerable VisitStatementEnumerable(CS.Syntax.StatementSyntax node) + { + return Visit(node); + } + + public SyntaxList VisitStatement(CS.Syntax.StatementSyntax node) + { + return Visit(node); + } + + private static VB.Syntax.StatementSyntax ConvertToStatement(SyntaxNode node) + { + if (node == null) + { + return null; + } + else if (node is VB.Syntax.StatementSyntax) + { + return (VB.Syntax.StatementSyntax)node; + } + else if (node is VB.Syntax.InvocationExpressionSyntax) + { + return VB.SyntaxFactory.ExpressionStatement((VB.Syntax.InvocationExpressionSyntax)node); + } + else + { + // can happen in error scenarios + return CreateBadStatement(((SyntaxNode)node).ToFullString(), typeof(VB.Syntax.StatementSyntax)); + } + } + + public override SyntaxList VisitBlock(CS.Syntax.BlockSyntax node) + { + List statements = node.Statements.SelectMany(VisitStatementEnumerable).ToList(); + + if (node.IsParentKind(CS.SyntaxKind.ConstructorDeclaration)) + { + ConstructorDeclarationSyntax constructor = (CS.Syntax.ConstructorDeclarationSyntax)node.Parent; + if (constructor.Initializer != null) + { + VB.Syntax.StatementSyntax initializer = nodeVisitor.Visit(constructor.Initializer); + statements.Insert(0, initializer); + } + } + + return List(statements); + } + + public override SyntaxList VisitLocalDeclarationStatement(CS.Syntax.LocalDeclarationStatementSyntax node) + { + SyntaxTriviaList leadingTrivia = TriviaList(node.GetFirstToken(includeSkipped: true).LeadingTrivia.SelectMany(nodeVisitor.VisitTrivia)); + + SyntaxToken token = node.Modifiers.Any(t => t.IsKind(CS.SyntaxKind.ConstKeyword)) + ? VB.SyntaxFactory.Token(leadingTrivia, VB.SyntaxKind.ConstKeyword) + : VB.SyntaxFactory.Token(leadingTrivia, VB.SyntaxKind.DimKeyword); + + return List( + VB.SyntaxFactory.FieldDeclaration( + new SyntaxList(), + SyntaxTokenList.Create(token), + SeparatedCommaList(node.Declaration.Variables.Select(nodeVisitor.Visit)))); + } + + public override SyntaxList VisitReturnStatement(CS.Syntax.ReturnStatementSyntax node) + { + return List( + VB.SyntaxFactory.ReturnStatement(nodeVisitor.VisitExpression(node.Expression))); + } + + public override SyntaxList VisitExpressionStatement(CS.Syntax.ExpressionStatementSyntax node) + { + return List( + nodeVisitor.VisitStatement(node.Expression)); + } + + public override SyntaxList VisitIfStatement(CS.Syntax.IfStatementSyntax node) + { + VB.Syntax.IfStatementSyntax ifStatement = VB.SyntaxFactory.IfStatement( + VB.SyntaxFactory.Token(VB.SyntaxKind.IfKeyword), + nodeVisitor.VisitExpression(node.Condition), + VB.SyntaxFactory.Token(VB.SyntaxKind.ThenKeyword)); + + List elseIfBlocks = new List(); + ElseClauseSyntax currentElseClause = node.Else; + while (currentElseClause != null) + { + if (!currentElseClause.Statement.IsKind(CS.SyntaxKind.IfStatement)) + { + break; + } + + IfStatementSyntax nestedIf = (CS.Syntax.IfStatementSyntax)currentElseClause.Statement; + currentElseClause = nestedIf.Else; + + VB.Syntax.ElseIfStatementSyntax elseIfStatement = VB.SyntaxFactory.ElseIfStatement( + VB.SyntaxFactory.Token(VB.SyntaxKind.ElseIfKeyword), + nodeVisitor.VisitExpression(nestedIf.Condition), + VB.SyntaxFactory.Token(VB.SyntaxKind.ThenKeyword)); + VB.Syntax.ElseIfBlockSyntax elseIfBlock = VB.SyntaxFactory.ElseIfBlock( + elseIfStatement, + Visit(nestedIf.Statement)); + elseIfBlocks.Add(elseIfBlock); + } + + return List( + VB.SyntaxFactory.MultiLineIfBlock( + ifStatement, + Visit(node.Statement), + List(elseIfBlocks), + currentElseClause == null ? null : nodeVisitor.Visit(currentElseClause))); + } + + public override SyntaxList VisitSwitchStatement(CS.Syntax.SwitchStatementSyntax node) + { + VB.Syntax.SelectStatementSyntax begin = VB.SyntaxFactory.SelectStatement( + expression: nodeVisitor.VisitExpression(node.Expression)); + + return List( + VB.SyntaxFactory.SelectBlock( + begin, + List(node.Sections.Select(nodeVisitor.Visit)))); + } + + public override SyntaxList VisitThrowStatement(CS.Syntax.ThrowStatementSyntax node) + { + return List( + VB.SyntaxFactory.ThrowStatement(nodeVisitor.VisitExpression(node.Expression))); + } + + public override SyntaxList VisitBreakStatement(CS.Syntax.BreakStatementSyntax node) + { + return List(VisitBreakStatementWorker(node)); + } + + private VB.Syntax.StatementSyntax VisitBreakStatementWorker(CS.Syntax.BreakStatementSyntax node) + { + foreach (SyntaxNode parent in node.GetAncestorsOrThis()) + { + if (parent.IsBreakableConstruct()) + { + switch (parent.Kind()) + { + case CS.SyntaxKind.DoStatement: + return VB.SyntaxFactory.ExitDoStatement(); + case CS.SyntaxKind.WhileStatement: + return VB.SyntaxFactory.ExitWhileStatement(); + case CS.SyntaxKind.SwitchStatement: + // If the 'break' is the last statement of a switch block, then we + // don't need to translate it into VB (as it is implied). + SwitchSectionSyntax outerSection = node.FirstAncestorOrSelf(); + if (outerSection != null && outerSection.Statements.Count > 0) + { + if (node == outerSection.Statements.Last()) + { + return VB.SyntaxFactory.EmptyStatement(); + } + } + + return VB.SyntaxFactory.ExitSelectStatement(); + case CS.SyntaxKind.ForStatement: + case CS.SyntaxKind.ForEachStatement: + return VB.SyntaxFactory.ExitForStatement(); + } + } + } + + return CreateBadStatement(node, nodeVisitor); + } + + public override SyntaxList VisitContinueStatement(CS.Syntax.ContinueStatementSyntax node) + { + return List(VisitContinueStatementWorker(node)); + } + + private VB.Syntax.StatementSyntax VisitContinueStatementWorker(CS.Syntax.ContinueStatementSyntax node) + { + foreach (SyntaxNode parent in node.GetAncestorsOrThis()) + { + if (parent.IsContinuableConstruct()) + { + switch (parent.Kind()) + { + case CS.SyntaxKind.DoStatement: + return VB.SyntaxFactory.ContinueDoStatement(); + case CS.SyntaxKind.WhileStatement: + return VB.SyntaxFactory.ContinueWhileStatement(); + case CS.SyntaxKind.ForStatement: + case CS.SyntaxKind.ForEachStatement: + return VB.SyntaxFactory.ContinueForStatement(); + } + } + } + + return CreateBadStatement(node, nodeVisitor); + } + + public override SyntaxList VisitWhileStatement(CS.Syntax.WhileStatementSyntax node) + { + VB.Syntax.WhileStatementSyntax begin = VB.SyntaxFactory.WhileStatement( + nodeVisitor.VisitExpression(node.Condition)); + + return List( + VB.SyntaxFactory.WhileBlock( + begin, + Visit(node.Statement))); + } + + public override SyntaxList VisitForEachStatement(CS.Syntax.ForEachStatementSyntax node) + { + VB.Syntax.ForEachStatementSyntax begin = VB.SyntaxFactory.ForEachStatement( + VB.SyntaxFactory.IdentifierName(nodeVisitor.ConvertIdentifier(node.Identifier)), + nodeVisitor.VisitExpression(node.Expression)); + + return List( + VB.SyntaxFactory.ForEachBlock( + begin, + Visit(node.Statement), + VB.SyntaxFactory.NextStatement())); + } + + public override SyntaxList VisitYieldStatement(CS.Syntax.YieldStatementSyntax node) + { + // map this to a return statement for now. + return List( + VB.SyntaxFactory.ReturnStatement(nodeVisitor.VisitExpression(node.Expression))); + } + + public override SyntaxList VisitDoStatement(CS.Syntax.DoStatementSyntax node) + { + VB.Syntax.DoStatementSyntax begin = VB.SyntaxFactory.SimpleDoStatement(); + + VB.Syntax.LoopStatementSyntax loop = VB.SyntaxFactory.LoopWhileStatement( + VB.SyntaxFactory.WhileClause(nodeVisitor.VisitExpression(node.Condition))); + + return List( + VB.SyntaxFactory.DoLoopWhileBlock( + begin, + Visit(node.Statement), + loop)); + } + + public override SyntaxList VisitUsingStatement(CS.Syntax.UsingStatementSyntax node) + { + VB.Syntax.UsingStatementSyntax usingStatement; + if (node.Expression != null) + { + usingStatement = VB.SyntaxFactory.UsingStatement().WithExpression( + nodeVisitor.VisitExpression(node.Expression)); + } + else + { + usingStatement = VB.SyntaxFactory.UsingStatement().WithVariables( + SeparatedCommaList(node.Declaration.Variables.Select(nodeVisitor.Visit))); + } + + return List( + VB.SyntaxFactory.UsingBlock( + usingStatement, + Visit(node.Statement))); + } + + public override SyntaxList VisitLabeledStatement(CS.Syntax.LabeledStatementSyntax node) + { + return List( + VB.SyntaxFactory.LabelStatement( + nodeVisitor.ConvertIdentifier(node.Identifier))); + } + + public override SyntaxList VisitGotoStatement(CS.Syntax.GotoStatementSyntax node) + { + return List(VisitGotoStatementWorker(node)); + } + + private VB.Syntax.StatementSyntax VisitGotoStatementWorker(CS.Syntax.GotoStatementSyntax node) + { + switch (node.Kind()) + { + case CS.SyntaxKind.GotoStatement: + return VB.SyntaxFactory.GoToStatement( + VB.SyntaxFactory.IdentifierLabel(nodeVisitor.ConvertIdentifier((CS.Syntax.IdentifierNameSyntax)node.Expression))); + case CS.SyntaxKind.GotoDefaultStatement: + return VB.SyntaxFactory.GoToStatement( + VB.SyntaxFactory.IdentifierLabel(VB.SyntaxFactory.Identifier("Else"))); + case CS.SyntaxKind.GotoCaseStatement: + string text = node.Expression.ToString(); + return VB.SyntaxFactory.GoToStatement( + VB.SyntaxFactory.IdentifierLabel(VB.SyntaxFactory.Identifier(text))); + } + + throw new NotImplementedException(); + } + + public override SyntaxList VisitEmptyStatement(CS.Syntax.EmptyStatementSyntax node) + { + return List(VB.SyntaxFactory.EmptyStatement()); + } + + public override SyntaxList VisitLockStatement(CS.Syntax.LockStatementSyntax node) + { + return List( + VB.SyntaxFactory.SyncLockBlock( + VB.SyntaxFactory.SyncLockStatement( + nodeVisitor.VisitExpression(node.Expression)), + Visit(node.Statement))); + } + + public override SyntaxList VisitTryStatement(CS.Syntax.TryStatementSyntax node) + { + return List( + VB.SyntaxFactory.TryBlock( + Visit(node.Block), + List(node.Catches.Select(nodeVisitor.Visit)), + nodeVisitor.Visit(node.Finally))); + } + + public override SyntaxList VisitFixedStatement(CS.Syntax.FixedStatementSyntax node) + { + // todo + return Visit(node.Statement); + } + + public override SyntaxList VisitUnsafeStatement(CS.Syntax.UnsafeStatementSyntax node) + { + return Visit(node.Block); + } + + public override SyntaxList VisitCheckedStatement(CS.Syntax.CheckedStatementSyntax node) + { + return Visit(node.Block); + } + + public override SyntaxList DefaultVisit(SyntaxNode node) + { + throw new NotImplementedException(); + } + } + } +} diff --git a/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Converting/Converter.cs b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Converting/Converter.cs new file mode 100644 index 000000000..7437e95c6 --- /dev/null +++ b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Converting/Converter.cs @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using CSharpToVisualBasicConverter.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using CS = Microsoft.CodeAnalysis.CSharp; +using VB = Microsoft.CodeAnalysis.VisualBasic; + +namespace CSharpToVisualBasicConverter +{ + public static partial class Converter + { + public static SyntaxNode Convert( + SyntaxTree syntaxTree, + IDictionary identifierMap = null, + bool convertStrings = false) + { + SourceText text = syntaxTree.GetText(); + SyntaxNode node = syntaxTree.GetRoot(); + + string vbText = Convert(text, node, identifierMap, convertStrings); + + return VB.SyntaxFactory.ParseSyntaxTree(vbText).GetRoot(); + } + + public static string Convert( + string text, + IDictionary identifierMap = null, + bool convertStrings = false) + { + List> parseFunctions = new List>() + { + s => CS.SyntaxFactory.ParseExpression(s), + s => CS.SyntaxFactory.ParseStatement(s), + }; + + foreach (Func parse in parseFunctions) + { + SyntaxNode node = parse(text); + SourceText stringText = SourceText.From(text); + + if (!node.ContainsDiagnostics && node.FullSpan.Length == text.Length) + { + return Convert(stringText, node, identifierMap, convertStrings); + } + } + + return Convert(CS.SyntaxFactory.ParseSyntaxTree(text), identifierMap, convertStrings).ToFullString(); + } + + private static string Convert( + SourceText text, + SyntaxNode node, + IDictionary identifierMap, + bool convertStrings) + { + if (node is CS.Syntax.StatementSyntax) + { + NodeVisitor nodeVisitor = new NodeVisitor(text, identifierMap, convertStrings); + StatementVisitor statementVisitor = new StatementVisitor(nodeVisitor, text); + SyntaxList vbStatements = statementVisitor.Visit(node); + + return string.Join(Environment.NewLine, vbStatements.Select(s => s.NormalizeWhitespace())); + } + else + { + NodeVisitor visitor = new NodeVisitor(text, identifierMap, convertStrings); + SyntaxNode vbNode = visitor.Visit(node); + + return vbNode.NormalizeWhitespace().ToFullString(); + } + } + + private static SeparatedSyntaxList SeparatedList(T value) + where T : SyntaxNode => VB.SyntaxFactory.SingletonSeparatedList(value); + + private static SyntaxTriviaList TriviaList(IEnumerable list) + => VB.SyntaxFactory.TriviaList(list); + + private static SyntaxList List(params T[] nodes) + where T : SyntaxNode => List(nodes.Where(n => n != null)); + + private static SyntaxList List(IEnumerable nodes) + where T : SyntaxNode => VB.SyntaxFactory.List(nodes); + + private static SeparatedSyntaxList SeparatedCommaList(IEnumerable nodes) + where T : SyntaxNode + { + IList nodesList = nodes as IList ?? nodes.ToList(); + List builder = new List(); + SyntaxToken token = VB.SyntaxFactory.Token(VB.SyntaxKind.CommaToken); + + bool first = true; + foreach (T node in nodes) + { + if (!first) + { + builder.Add(token); + } + + first = false; + builder.Add(node); + } + + return VB.SyntaxFactory.SeparatedList(builder); + } + + private static string RemoveNewLines(string text) => + text.Replace("\r\n", " ").Replace("\r", " ").Replace("\n", " "); + + private static string CreateCouldNotBeConvertedText(string text, Type type) + => "'" + RemoveNewLines(text) + "' could not be converted to a " + type.Name; + + private static string CreateCouldNotBeConvertedComment(string text, Type type) + => "' " + CreateCouldNotBeConvertedText(text, type); + + private static string CreateCouldNotBeConvertedString(string text, Type type) + => "\"" + CreateCouldNotBeConvertedText(text, type) + "\""; + + private static VB.Syntax.StatementSyntax CreateBadStatement(string text, Type type) + { + string comment = CreateCouldNotBeConvertedComment(text, type); + SyntaxTrivia trivia = VB.SyntaxFactory.CommentTrivia(comment); + + SyntaxToken token = VB.SyntaxFactory.Token(SyntaxTriviaList.Create(trivia), VB.SyntaxKind.EmptyToken); + return VB.SyntaxFactory.EmptyStatement(token); + } + + private static VB.Syntax.StatementSyntax CreateBadStatement(SyntaxNode node, NodeVisitor visitor) + { + IEnumerable leadingTrivia = node.GetFirstToken(includeSkipped: true).LeadingTrivia.SelectMany(visitor.VisitTrivia); + IEnumerable trailingTrivia = node.GetLastToken(includeSkipped: true).TrailingTrivia.SelectMany(visitor.VisitTrivia); + + string comment = CreateCouldNotBeConvertedComment(node.ToString(), typeof(VB.Syntax.StatementSyntax)); + leadingTrivia = leadingTrivia.Concat( + VB.SyntaxFactory.CommentTrivia(comment)); + + SyntaxToken token = VB.SyntaxFactory.Token(TriviaList(leadingTrivia), VB.SyntaxKind.EmptyToken, trailing: TriviaList(trailingTrivia)); + return VB.SyntaxFactory.EmptyStatement(token); + } + + private static VB.Syntax.StructuredTriviaSyntax CreateBadDirective(SyntaxNode node, NodeVisitor visitor) + { + IEnumerable leadingTrivia = node.GetFirstToken(includeSkipped: true).LeadingTrivia.SelectMany(visitor.VisitTrivia).Where(t => !t.IsKind(VB.SyntaxKind.EndOfLineTrivia)); + IEnumerable trailingTrivia = node.GetLastToken(includeSkipped: true).TrailingTrivia.SelectMany(visitor.VisitTrivia).Where(t => !t.IsKind(VB.SyntaxKind.EndOfLineTrivia)); + + string comment = CreateCouldNotBeConvertedComment(node.ToString(), typeof(VB.Syntax.StatementSyntax)); + leadingTrivia = leadingTrivia.Concat( + VB.SyntaxFactory.CommentTrivia(comment)); + + SyntaxToken token = VB.SyntaxFactory.Token(TriviaList(leadingTrivia), VB.SyntaxKind.HashToken, trailing: TriviaList(trailingTrivia), text: ""); + return VB.SyntaxFactory.BadDirectiveTrivia(token); + } + } +} diff --git a/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Utilities/CSharpExtensions.cs b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Utilities/CSharpExtensions.cs new file mode 100644 index 000000000..e2eb52bec --- /dev/null +++ b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Utilities/CSharpExtensions.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CSharpToVisualBasicConverter.Utilities +{ + internal static class CSharpExtensions + { + public static IEnumerable GetAncestorsOrThis(this SyntaxNode node, bool allowStructuredTrivia = false) + where T : SyntaxNode + { + SyntaxNode current = node; + while (current != null) + { + if (current is T) + { + yield return (T)current; + } + + if (allowStructuredTrivia && + current.IsStructuredTrivia && + current.Parent == null) + { + StructuredTriviaSyntax structuredTrivia = (StructuredTriviaSyntax)current; + SyntaxTrivia parentTrivia = structuredTrivia.ParentTrivia; + current = parentTrivia.Token.Parent; + } + else + { + current = current.Parent; + } + } + } + + public static SyntaxNode GetParent(this SyntaxTree syntaxTree, SyntaxNode node) => node?.Parent; + + public static TypeSyntax GetVariableType(this VariableDeclaratorSyntax variable) + { + if (!(variable.Parent is VariableDeclarationSyntax parent)) + { + return null; + } + + return parent.Type; + } + + public static bool IsBreakableConstruct(this SyntaxNode node) + { + switch (node.Kind()) + { + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.SwitchStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForEachStatement: + return true; + } + + return false; + } + + public static bool IsContinuableConstruct(this SyntaxNode node) + { + switch (node.Kind()) + { + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForEachStatement: + return true; + } + + return false; + } + + public static bool IsParentKind(this SyntaxNode node, SyntaxKind kind) + => node?.Parent.IsKind(kind) == true; + } +} diff --git a/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Utilities/EnumerableExtensions.cs b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Utilities/EnumerableExtensions.cs new file mode 100644 index 000000000..4bbef1b82 --- /dev/null +++ b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Utilities/EnumerableExtensions.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace CSharpToVisualBasicConverter.Utilities +{ + internal static class EnumerableExtensions + { + private static IEnumerable ConcatWorker(this IEnumerable source, T value) + { + foreach (T v in source) + { + yield return v; + } + + yield return value; + } + + public static IEnumerable Concat(this IEnumerable source, T value) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return source.ConcatWorker(value); + } + } +} diff --git a/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Utilities/StringExtensions.cs b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Utilities/StringExtensions.cs new file mode 100644 index 000000000..203a9e4c4 --- /dev/null +++ b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Lib/Utilities/StringExtensions.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text; + +namespace CSharpToVisualBasicConverter.Utilities +{ + internal static class StringExtensions + { + public static string Repeat(this string s, int count) + { + if (s == null) + { + throw new ArgumentNullException("s"); + } + + if (count == 0 || s.Length == 0) + { + return string.Empty; + } + else if (count == 1) + { + return s; + } + else + { + StringBuilder builder = new StringBuilder(s.Length * count); + for (int i = 0; i < count; i++) + { + builder.Append(s); + } + + return builder.ToString(); + } + } + } +} diff --git a/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Test/CSharpToVisualBasicConverter.UnitTests.csproj b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Test/CSharpToVisualBasicConverter.UnitTests.csproj new file mode 100644 index 000000000..b9857fd93 --- /dev/null +++ b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Test/CSharpToVisualBasicConverter.UnitTests.csproj @@ -0,0 +1,24 @@ + + + + netcoreapp2.0 + false + + + + + + + + + + + + + + + + + + + diff --git a/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Test/CSharpToVisualBasicConverterTests.cs b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Test/CSharpToVisualBasicConverterTests.cs new file mode 100644 index 000000000..347182084 --- /dev/null +++ b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Test/CSharpToVisualBasicConverterTests.cs @@ -0,0 +1,446 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using CSharpToVisualBasicConverter; +using CSharpToVisualBasicConverter.UnitTests.TestFiles; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Xunit; + +namespace CSharpToVisualBasicConverter.UnitTests.Converting +{ + public class CSharpToVisualBasicConverterTests + { + [Fact(Skip = "Not Yet Implemented")] + public void TestAllConstructs() + { + string csharpConstructs = TestFilesHelper.GetFile("AllConstructs.cs"); + Microsoft.CodeAnalysis.SyntaxNode vbActualConstructs = Converter.Convert(SyntaxFactory.ParseSyntaxTree(csharpConstructs)); + + string vbActual = vbActualConstructs.ToFullString(); + string vbExpected = TestFilesHelper.GetFile("AllConstructs.txt"); + Assert.Equal(vbExpected, vbActual); + } + + [Fact] + public void TestParseAddExpression() + { + string csharpCode = "1+2"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal("1 + 2", vbNode); + } + + [Fact] + public void TestParseInvocationExpression() + { + string csharpCode = " Console . WriteLine ( ) "; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal("Console.WriteLine()", vbNode); + } + + [Fact] + public void TestParseLambdaExpression() + { + string csharpCode = "a => b + c"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal("Function(a) b + c", vbNode); + } + + [Fact] + public void TestParseReturnStatement() + { + string csharpCode = " return Console . WriteLine ( ) ; "; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal("Return Console.WriteLine()", vbNode); + } + + [Fact] + public void TestParseFieldNoModifier() + { + string csharpCode = "class Test { int i; }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal( +@"Class Test + + Dim i As Integer +End Class +", vbNode); + } + + [Fact] + public void TestParseStaticClass() + { + string csharpCode = "static class Test { }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal( +@"Module Test +End Module +", vbNode); + } + + [Fact] + public void TestParseObjectInitializerTwoInitializers() + { + string csharpCode = "new object { X = null, Y = null }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal("New Object With {.X = Nothing, .Y = Nothing}", vbNode); + } + + [Fact] + public void TestParseAnonymousTypeTwoInitializers() + { + string csharpCode = "new { X = null, Y = null }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal("New With {.X = Nothing, .Y = Nothing}", vbNode); + } + + [Fact] + public void TestParseCollectionInitializer() + { + string csharpCode = "new Dictionary { { 0, \"\"} }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal("New Dictionary(Of Integer, String) From {{0, \"\"}}", vbNode); + } + + [Fact] + public void TestParseAbstractClass() + { + string csharpCode = "abstract class Test { }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal( +@"MustInherit Class Test +End Class +", vbNode); + } + + [Fact] + public void TestParseExtensionMethod() + { + string csharpCode = "static class Test { public static int Foo(this string s) { } }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal( +@"Module Test + + + Public Function Foo(s As String) As Integer + End Function +End Module +", vbNode); + } + + [Fact(Skip = "Not Yet Implemented")] + public void TestParseDocComments() + { + string csharpCode = +@" + /// + /// On the Insert tab, the galleries include items that are designed to coordinate with the + /// overall look of your document. You can use these galleries to insert tables, headers, + /// footers, lists, cover pages, and other document building blocks. When you create pictures, + /// charts, or diagrams, they also coordinate with your current document look. + /// + class Test + { + /// + /// You can easily change the formatting of selected text in the document text by choosing a + /// look for the selected text from the Quick Styles gallery on the Home tab. You can also + /// format text directly by using the other controls on the Home tab. Most controls offer a + /// choice of using the look from the current theme or using a format that you specify directly. + /// + void Foo() + { + } + }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal( +@"''' +''' On the Insert tab, the galleries include items that are designed to coordinate with the +''' overall look of your document. You can use these galleries to insert tables, headers, +''' footers, lists, cover pages, and other document building blocks. When you create pictures, +''' charts, or diagrams, they also coordinate with your current document look. +''' +Class Test + + ''' + ''' You can easily change the formatting of selected text in the document text by choosing a + ''' look for the selected text from the Quick Styles gallery on the Home tab. You can also + ''' format text directly by using the other controls on the Home tab. Most controls offer a + ''' choice of using the look from the current theme or using a format that you specify directly. + ''' + Sub Foo() + End Sub +End Class + +", vbNode); + } + + [Fact] + public void TestParseExtensionMethodDocComment() + { + string csharpCode = +@"static class C +{ + /// + /// Method summary + /// + static void M(this object o) + { + } +} +"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal( +@"Module C + + ''' + ''' Method summary + ''' + + Sub M(o As Object) + End Sub +End Module +", vbNode); + } + + [Fact] + public void TestForStatement1() + { + string csharpCode = "for (int i = 0; i < 10; i++) { }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal(@"For i = 0 To 10 - 1 +Next", vbNode); + } + + [Fact] + public void TestForStatement2() + { + string csharpCode = "for (int i = 0; i <= 10; i++) { }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal(@"For i = 0 To 10 +Next", vbNode); + } + + [Fact] + public void TestForStatement3() + { + string csharpCode = "for (int i = 0; i <= 10; i += 1) { }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal(@"For i = 0 To 10 Step 1 +Next", vbNode); + } + + [Fact] + public void TestForStatement4() + { + string csharpCode = "for (int i = 0; i <= 10; i += 2) { }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal(@"For i = 0 To 10 Step 2 +Next", vbNode); + } + + [Fact] + public void TestForStatement5() + { + string csharpCode = "for (var i = 0; i <= 10; i += 2) { }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal(@"For i = 0 To 10 Step 2 +Next", vbNode); + } + + [Fact] + public void TestForStatement6() + { + string csharpCode = "for (; i <= 10; i += 2) { }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal(@"While i <= 10 + i += 2 +End While", vbNode); + } + + [Fact] + public void TestForStatement7() + { + string csharpCode = "for (var i = 0; ; i += 2) { }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal(@"Dim i = 0 +While True + i += 2 +End While", vbNode); + } + + [Fact] + public void TestForStatement8() + { + string csharpCode = "for (var i = 0; i <= 10; ) { }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal(@"Dim i = 0 +While i <= 10 +End While", vbNode); + } + + [Fact] + public void TestForStatement9() + { + string csharpCode = "for (int i = 0; i <= 10; i++) Console.WriteLine(a);"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal(@"For i = 0 To 10 + Console.WriteLine(a) +Next", vbNode); + } + + [Fact] + public void TestForStatement10() + { + string csharpCode = "for (int i = 0; i <= 10; i++) { Console.WriteLine(a); }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal(@"For i = 0 To 10 + Console.WriteLine(a) +Next", vbNode); + } + + [Fact] + public void TestForStatement11() + { + string csharpCode = "for (int i = 0; i <= 10; i++) { Console.WriteLine(a); Console.WriteLine(b); }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal(@"For i = 0 To 10 + Console.WriteLine(a) + Console.WriteLine(b) +Next", vbNode); + } + + [Fact] + public void TestForStatement12() + { + string csharpCode = "for (int i = x; i >= 0; i--) { }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal(@"For i = x To 0 Step -1 +Next", vbNode); + } + + [Fact] + public void TestForStatement13() + { + string csharpCode = "for (int i = x; i > y; i -= 2) { }"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal(@"For i = x To y + 1 Step -2 +Next", vbNode); + } + + [Fact] + public void TestAsyncModifier() + { + string csharpCode = +@"async void M() +{ +} + +async Task N() +{ +} + +async Task O() +{ +}"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal( +@"Async Sub M() +End Sub + +Async Function N() As Task +End Function + +Async Function O() As Task(Of Integer) +End Function +", +vbNode); + } + + [Fact(Skip = "Not Yet Implemented")] + public void TestAwaitExpression() + { + string csharpCode = +@"async void Button1_Click(object sender, EventArgs e) +{ + ResultsTextBox.Text = await httpClient.DownloadStringTaskAsync(""http://somewhere.com/""); +}"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal( +@"Async Sub Button1_Click(sender As Object, e As EventArgs) + ResultsTextBox.Text = Await httpClient.DownloadStringTaskAsync(""http://somewhere.com/"") +End Sub +", +vbNode); + } + + [Fact(Skip = "Not Yet Implemented")] + public void TestAwaitStatement() + { + string csharpCode = +@"async void Button1_Click(object sender, EventArgs e) +{ + await BeepAsync(); +}"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal( +@"Async Sub Button1_Click(sender As Object, e As EventArgs) + Await BeepAsync() +End Sub +", +vbNode); + } + + [Fact(Skip = "Not Yet Implemented")] + public void TestAsyncLambdas() + { + // TODO: In C#, whether an async lambda is void returning or Task returning cannot be determined syntactically. + // When semantic-aware translation is implemented we should revisit this to ensure that we translate accurately. + // In the meantime let's prefer to translate async lambdas as Async Function lambdas in VB, since they're most common + // and recommended. + string csharpCode = +@"void M() +{ + Task.Run(async () => {}); + Task.Run(async () => await NAsync()); +}"; + string vbNode = Converter.Convert(csharpCode); + + Assert.Equal( +@"Sub M() + Task.Run(Async Function() + End Function) + Task.Run(Async Function() Await NAsync()) +End Sub +", +vbNode); + } + } +} diff --git a/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Test/TestFiles/AllConstructs.cs b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Test/TestFiles/AllConstructs.cs new file mode 100644 index 000000000..6b639ac20 --- /dev/null +++ b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Test/TestFiles/AllConstructs.cs @@ -0,0 +1,775 @@ +// ********************************************************* +// +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES +// OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, +// INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES +// OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT. +// +// See the Apache 2 License for the specific language +// governing permissions and limitations under the License. +// +// ********************************************************* + +#error Error message +#warning Warning message +#pragma warning disable 414, 3021 +#pragma warning restore 3021 +#line 6 +#line 2 "test.cs" +#line default +#line hidden +#define foo +#if foo +#else +#endif +#undef foo + +extern alias Foo; + +using System; +using System.Collections.Generic; + +#if DEBUG || TRACE +using System.Diagnostics; +#elif SILVERLIGHT +using System.Diagnostics; +#else +using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +#endif + +#region Region + +#region more +using ConsoleApplication2.Test; +using M = System.Math; +using X = ABC.X; +#endregion +using X = int1; + +#endregion + +[assembly: System.Copyright(@"(C) 2014")] +[module: System.Copyright("\n\t\u0123(C) 2014" + "\u0123")] + +class TopLevelType : IDisposable +{ + /// + /// This is the dispose implementation. + /// + public void IDisposable.Dispose() { } + + /// + /// This is a function with a parameter + /// + /// s is a string + /// + public boolean F1(string s) { } +} + +namespace My +{ + using A.B; + + interface CoContra { } + delegate void CoContra2 () where T : struct; + + public unsafe partial class A : C, I + { + [method: Obsolete] + public A([param: Obsolete] int foo) : + base(1) + { + L: + { + int i = sizeof(int); + ++i; + } + +#if DEBUG + Console.WriteLine(export.iefSupplied.command); +#endif + const int? local = int.MaxValue; + const Guid? local0 = new Guid(r.ToString()); + + var привет = local; + var мир = local; + var local3 = 0, local4 = 1; + local3 = local4 = 1; + var local5 = null as Action ?? null; + var local6 = local5 is Action; + + var u = 1u; + var U = 1U; + long hex = 0xBADC0DE, Hex = 0XDEADBEEF, l = -1L, L = 1L, l2 = 2l; + ulong ul = 1ul, Ul = 1Ul, uL = 1uL, UL = 1UL, + lu = 1lu, Lu = 1Lu, lU = 1lU, LU = 1LU; + + bool @bool; + byte @byte; + char @char = 'c', \u0066 = '\u0066', hexchar = '\x0130', hexchar2 = (char)0xBAD; + string \U00000065 = "\U00000065"; + decimal @decimal = 1.44M; + dynamic @dynamic; + double @double = M.PI; + float @float = 1.2f; + int @int = local ?? -1; + long @long; + object @object; + sbyte @sbyte; + short @short; + string @string = @"""/*"; + uint @uint; + ulong @ulong; + ushort @ushort; + + dynamic dynamic = local5; + var add = 0; + var ascending = 0; + var descending = 0; + var from = 0; + var get = 0; + var global = 0; + var group = 0; + var into = 0; + var join = 0; + var let = 0; + var orderby = 0; + var partial = 0; + var remove = 0; + var select = 0; + var set = 0; + var value = 0; + var var = 0; + var where = 0; + var yield = 0; + + if (i > 0) + { + return; + } + else if (i == 0) + { + throw new Exception(); + } + var o1 = new MyObject(); + var o2 = new MyObject(var); + var o3 = new MyObject { A = i }; + var o4 = new MyObject(@dynamic) + { + A = 0, + B = 0, + C = 0 + }; + var o5 = new { A = 0 }; + var dictionaryInitializer = new Dictionary + { + {1, ""}, + {2, "a"} + }; + float[] a = new float[] + { + 0f, + 1.1f + }; + int[] arrayTypeInference = new[] { 0, 1, }; + switch (i) + { + case 1: + { + goto case 2; + } + case 2: + { + goto default; + break; + } + default: + { + return; + } + } + while (i < 10) + { + ++i; + } + do + { + ++i; + } + while (i < 10); + for (int j = 0; j < 100; ++j) + { + Console.WriteLine(j); + } + foreach (var i in Items()) + { + if (i == 7) + return; + else + continue; + } + checked + { + checked(++i); + } + unchecked + { + unchecked(++i); + } + lock (sync) + process(); + using (var v = BeginScope()) + using (A a = new A()) + using (BeginScope()) + return; + yield return this.items[3]; + yield break; + fixed (int* p = stackalloc int[100]) + { + *intref = 1; + } + unsafe + { + int* p = null; + } + try + { + throw null; + } + catch (System.AccessViolationException av) + { + throw av; + } + catch (Exception) + { + throw; + } + finally + { + try { } catch { } + } + var anonymous = + { + A = 1, + B = 2, + C = 3, + }; + var query = from c in customers + let d = c + where d != null + join c1 in customers on c1.GetHashCode() equals c.GetHashCode() + join c1 in customers on c1.GetHashCode() equals c.GetHashCode() into e + group c by c.Country + into g + orderby g.Count() ascending + orderby g.Key descending + select new { Country = g.Key, CustCount = g.Count() }; + } + ~A() + { + } + private readonly int f1; + [Obsolete] + [NonExisting] + [Foo::NonExisting(var, 5)] + [CLSCompliant(false)] + [Obsolete, System.NonSerialized, NonSerialized, CLSCompliant(true || false & true)] + private volatile int f2; + [return: Obsolete] + [method: Obsolete] + public void Handler(object value) + { + } + public int m(T t) + where T : class, new() + { + base.m(t); + return 1; + } + public string P + { + get + { + return "A"; + } + set; + } + public abstract string P + { + get; + } + public abstract int this[int index] + { + protected internal get; + internal protected set; + } + [method: Obsolete] + [field: Obsolete] + [event: Obsolete] + public readonly event Event E; + [event: Test] + public event Action E1 + { + [Obsolete] + add { value = value; } + [Obsolete] + [return: Obsolete] + remove { } + } + public static A operator +(A first, A second) + { + Delegate handler = new Delegate(Handler); + return first.Add(second); + } + [method: Obsolete] + [return: Obsolete] + public static bool operator true(A a) + { + return true; + } + public static bool operator false(A a) + { + return false; + } + class C + { + } + } + public struct S : I + { + public S() + { + } + private int f1; + [Obsolete] + private volatile int f2; + public abstract int m(T t) + where T : struct + { + return 1; + } + public string P + { + get + { + int value = 0; + return "A"; + } + set; + } + public abstract string P { get; } + public abstract string P1 { set; } + public abstract string P2 { get; set; } + + public abstract int this[int index] + { + get; + internal protected set; + } + public event Event E; + public static A operator +(A first, A second) + { + return first.Add(second); + } + fixed int field[10]; + class C + { + } + } + public interface I + { + void A(int value); + int B(int value); + string Value { get; set; } + string Value1 { get; } + string Value2 { set; } + } + [type: Flags] + public enum E + { + A, + B = A, + C = 2 + A, + +#if DEBUG + D, +#endif + + } + public delegate void Delegate(object P); + namespace Test + { + using System; + using System.Collections; + public class Список + { + public static IEnumerable Power(int number, int exponent) + { + Список Список = new Список(); + Список.Main(); + int counter = 0; + int אתר = 0; + while (++counter++ < --exponent--) + { + result = result * number + +number+++++number; + yield return result; + } + } + static void Main() + { + foreach (int i in Power(2, 8)) + { + Console.Write("{0} ", i); + } + } + } + } +} + +namespace ConsoleApplication1 +{ + namespace RecursiveGenericBaseType + { + class A : B, A> + { + protected virtual A M() { } + protected abstract B, A> N() { } + static B, A> O() { } + } + + sealed class B : A> + { + protected override A M() { } + protected sealed override B, A> N() { } + new static A O() { } + } + } + + namespace Boo + { + public class Bar where T : IComparable + { + public T f; + public class Foo : IEnumerable + { + public void Method(K k, T t, U u) + where K : IList, IList, IList + where V : IList + { + A a; + } + } + } + } + + class Test + { + void Bar3() + { + var x = new Boo.Bar.Foo(); + x.Method(" ", 5, new object()); + + var q = from i in new int[] { 1, 2, 3, 4 } + where i > 5 + select i; + } + + public static implicit operator Test(string s) + { + return new ConsoleApplication1.Test(); + } + public static explicit operator Test(string s) + { + return new Test(); + } + + public int foo = 5; + void Bar2() + { + foo = 6; + this.Foo = 5.GetType(); Test t = "sss"; + } + + public event EventHandler MyEvent = delegate { }; + + void Blah() + { + int i = 5; + int? j = 6; + + Expression> e = () => i; + Expression> e2 = b => () => { return; }; + Func f = delegate (bool a) + { + return !a; + }; + Action a = Blah; + } + + public Type Foo + { + [Obsolete("Name", error = false)] + get + { + return typeof(IEnumerable<>); + } + set + { + var t = typeof(System.Int32); + t.ToString(); + t = value; + } + } + + public void Constants() + { + int i = 1 + 2 + 3 + 5; + global::System.String s = "a" + (System.String)"a" + "a" + "a" + "a" + "A"; + } + + public void ConstructedType() + { + List i = null; + int c = i.Count; + } + } +} + +namespace Comments.XmlComments.UndocumentedKeywords +{ + /// + /// Whatever + /// + /// + /// // + /// /* */ + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + class /*///*/C + { + void M(T t, U u) + { + // comment + /* *** / */ + /* // + */ + /*s*///comment + // /***/ + /*s*/int /*s*/intValue = 0; + intValue = intValue /*s*/+ 1; + string strValue = /*s*/"hello"; + /*s*/MyClass c = new MyClass(); + string verbatimStr = /*s*/@"\\\\"; + string verbatimStr2 = @"line 1 +line2"; + } + } + + /** + * + * Whatever + * + * + * // + * + * + * + * + * + * + * + * + * + * + * + */ + class A /*Scen8*/{ } + + class B /*Scen9*/{ } + + class yield + { + void Foo(__arglist) + { + C c = null; + c.M(5, default(U)); + TypedReference tr = __makeref(c); + Type t = __reftype(tr); + int j = __refvalue(tr, int); + Params(a: t, b: t); + } + void Params(ref dynamic a, out dynamic b, params dynamic[] c) {} + void Params(out dynamic a = 2, ref dynamic c = default(dynamic), params dynamic[][] c) {} + + public override string ToString() { return base.ToString(); } + + public partial void OnError(); + + public partial void method() + { + int?[] a = new int?[5];/*[] bug*/ // YES [] + int[] var = { 1, 2, 3, 4, 5 };/*,;*/ + int i = a[i];/*[]*/ + Foo f = new Foo();/*<> ()*/ + f.method();/*().*/ + i = i + i - i * i / i % i & i | i ^ i;/*+ - * / % & | ^*/ + bool b = true & false | true ^ false;/*& | ^*/ + b = !b;/*!*/ + i = ~i;/*~i*/ + b = i < i && i > i;/*< && >*/ + int? ii = 5;/*? bug*/ // NO ? + int f = true ? 1 : 0;/*? :*/ // YES : + i++;/*++*/ + i--;/*--*/ + b = true && false || true;/*&& ||*/ + i << 5;/*<<*/ + i >> 5;/*>>*/ + b = i == i && i != i && i <= i && i >= i;/*= == && != <= >=*/ + i += 5.0;/*+=*/ + i -= i;/*-=*/ + i *= i;/**=*/ + i /= i;/*/=*/ + i %= i;/*%=*/ + i &= i;/*&=*/ + i |= i;/*|=*/ + i ^= i;/*^=*/ + i <<= i;/*<<=*/ + i >>= i;/*>>=*/ + object s = x => x + 1;/*=>*/ + Point point; + unsafe + { + Point* p = &point;/** &*/ + p->x = 10;/*->*/ + } + IO::BinaryReader br = null; + } + + struct Point { public int X; public int Y; } + } + + class Bugs + { + // Keywords should be escaped in VB + int Next; + + void RedundantBreakStatements(int i) + { + // Don't include trailing 'break' statements at the end of a case section, they're + // redundant in VB. + switch (i) + { + case 0: + if (true) + { + break; + } + else + { + break; + } + break; + + case 1: + Console.WriteLine(a); + break; + Console.WriteLine(b); + break; + + default: + Console.WriteLine(c); + break; + Console.WriteLine(d); + break; + } + } + + void HexadecimalConstants() + { + Console.WriteLine(0x0); + Console.WriteLine(0x1); + Console.WriteLine(0x10); + Console.WriteLine(0xFFFFFFFF); + Console.WriteLine(0xffffffff); + } + + void NestedIfElse() + { + if (a < b) + { + Console.WriteLine("a < b"); + } + else if (c < d) + { + Console.WriteLine("c < d"); + } + else if (e < f) + { + Console.WriteLine("e < f"); + } + else + { + Console.WriteLine("else"); + } + } + } + + abstract class AbstractClass + { + protected abstract void AbstractMethod(); + } + + class Constructors + { + public Constructors() + : this() + { + } + + public Constructors() + : this(a) + { + } + + public Constructors() + : this(a, b) + { + } + + public Constructors() + : base() + { + } + + public Constructors() + : base(a) + { + } + + public Constructors() + : base(a, b) + { + } + } +} diff --git a/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Test/TestFiles/AllConstructs.txt b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Test/TestFiles/AllConstructs.txt new file mode 100644 index 000000000..3de93a08e --- /dev/null +++ b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Test/TestFiles/AllConstructs.txt @@ -0,0 +1,707 @@ +' '#error Error message' could not be converted to a StatementSyntax +' '#warning Warning message' could not be converted to a StatementSyntax +' '#pragma warning disable 414, 3021' could not be converted to a StatementSyntax +' '#pragma warning restore 3021' could not be converted to a StatementSyntax +' '#line 6' could not be converted to a StatementSyntax +' '#line 2 "test.cs"' could not be converted to a StatementSyntax +' '#line default' could not be converted to a StatementSyntax +' '#line hidden' could not be converted to a StatementSyntax +' '#define foo' could not be converted to a StatementSyntax +#If foo +#Else +#End If +' '#undef foo' could not be converted to a StatementSyntax +' 'extern alias Foo;' could not be converted to a ImportsStatementSyntax + +Imports System +Imports System.Collections.Generic +Imports System.Linq +Imports System.Linq.Expressions +Imports System.Text +Imports M = System.Math +#If DEBUG OrElse TRACE +using System.Diagnostics; +#ElseIf SILVERLIGHT +using System.Diagnostics; +#Else +Imports System.Diagnostics +#End If +#Region +#Region +Imports ConsoleApplication2.Test +#End Region +Imports X = int1 +Imports X = ABC.X(Of Integer) + + + +Class TopLevelType + Implements IDisposable + + ''' + ''' This is the dispose implementation. + ''' + Public Sub Dispose() Implements IDisposable.Dispose + End Sub + + ''' + ''' This is a function with a parameter + ''' + ''' s is a string + ''' + Public Function F1(s As String) As [boolean] + End Function +End Class + +Namespace My + + Interface CoContra(Of Out T, In K) + + End Interface + + Delegate Sub CoContra2(Of Out T, In K)() + + Public unsafe Partial Class A + Inherits C + Implements I + + + Public Sub New( foo As Integer) + MyBase.New(1) +L +#If DEBUG + Console.WriteLine(export.iefSupplied.command); +#End If + Const local As Integer? = Integer.MaxValue + Const local0 As Guid? = New Guid(r.ToString()) + Dim привет = local + Dim мир = local + Dim local3 = 0, local4 = 1 + local3 = "'local4=1' could not be converted to a ExpressionSyntax" + Dim local5 = If(TryCast(Nothing, Action), Nothing) + Dim local6 = TypeOf local5 Is Action + Dim u = 1u + Dim U = 1U + Dim hex As Long = &HBADC0DE, Hex As Long = &HDEADBEEF, l As Long = -1L, L As Long = 1L, l2 As Long = 2l + Dim ul As ULong = 1ul, Ul As ULong = 1Ul, uL As ULong = 1uL, UL As ULong = 1UL, lu As ULong = 1lu, Lu As ULong = 1Lu, lU As ULong = 1lU, LU As ULong = 1LU + Dim bool As Boolean + Dim [byte] As Byte + Dim [char] As Char = "c"c, f As Char = "\u0066"c, hexchar As Char = "\x0130"c, hexchar2 As Char = DirectCast(&HBAD, Char) + Dim e As String = "\U00000065" + Dim [decimal] As Decimal = 1.44M + Dim dynamic As dynamic + Dim [double] As Double = M.PI + Dim float As Single = 1.2f + Dim int As Integer = If(local, -1) + Dim [long] As Long + Dim [object] As Object + Dim [sbyte] As SByte + Dim [short] As Short + Dim [string] As String = """/*" + Dim uint As UInteger + Dim [ulong] As ULong + Dim [ushort] As UShort + Dim dynamic As dynamic = local5 + Dim add = 0 + Dim ascending = 0 + Dim descending = 0 + Dim from = 0 + Dim [get] = 0 + Dim [global] = 0 + Dim group = 0 + Dim into = 0 + Dim join = 0 + Dim [let] = 0 + Dim orderby = 0 + Dim [partial] = 0 + Dim remove = 0 + Dim [select] = 0 + Dim [set] = 0 + Dim value = 0 + Dim var = 0 + Dim where = 0 + Dim yield = 0 + If i > 0 + Return + ElseIf i = 0 + Throw New Exception() + End If + + Dim o1 = New MyObject() + Dim o2 = New MyObject(var) + Dim o3 = New MyObject With {.A = i} + Dim o4 = New MyObject(dynamic) With {.A = 0, .B = 0, .C = 0} + Dim o5 = New With {.A = 0} + Dim dictionaryInitializer = New Dictionary(Of Integer, String) From {{1, ""}, {2, "a"}} + Dim a As Single() = New Single() {0f, 1.1f} + Dim arrayTypeInference As Integer() = {0, 1} + Select i + Case 1 + GoTo 2 + Case 2 + GoTo Else + Exit Select + Case Else + Return + End Select + + While i < 10 + i = i + 1 + End While + + Do + i = i + 1 + Loop While i < 10 + + Dim j As Integer = 0 + + While j < 100 + Console.WriteLine(j) + j = j + 1 + End While + + For Each i In Items() + If i = 7 + Return + Else + Continue For + End If + Next + + Checked("'i=i+1' could not be converted to a ExpressionSyntax") + Unchecked("'i=i+1' could not be converted to a ExpressionSyntax") + SyncLock sync + process() + End SyncLock + + Using v = BeginScope() + Using a As A = New A() + Using BeginScope() + Return + End Using + End Using + End Using + + Return Me.items(3) + Return + intref = 1 + Dim p As Integer = Nothing + Try + Throw Nothing + Catch av As System.AccessViolationException + Throw av + Catch As Exception + Throw + Finally + Try + Catch + End Try + End Try + + Dim anonymous = {"'A=1' could not be converted to a ExpressionSyntax", "'B=2' could not be converted to a ExpressionSyntax", "'C=3' could not be converted to a ExpressionSyntax"} + Dim query = From c In customers Let d = c Where d IsNot Nothing Join c1 In customers On c1.GetHashCode() Equals c.GetHashCode() Group Join c1 In customers On c1.GetHashCode() Equals c.GetHashCode() Into e = Group Group c By c.Country Into g = Group + End Sub + + Sub Finalize() + End Sub + + Private ReadOnly f1 As Integer + + + + + + + Private volatile f2 As Integer + + + + Public Sub Handler(value As Object) + End Sub + + Public Function m(Of T)(t As T) As Integer + MyBase.m(t) + Return 1 + End Function + + Public Property P As String + Get + Return "A" + End Get + + Set + End Set + End Property + + Public MustOverride ReadOnly Property P As String + + Public MustOverride Property Item(index As Integer) As Integer + Protected Friend Get + End Get + + Friend Protected Set + End Set + End Property + + + + + Public ReadOnly Event E As [Event] + + + Public Event E1 As Action + + AddHandler + value = value + End AddHandler + + + + RemoveHandler + End RemoveHandler + End Event + + Public Shared Operator +(first As A, second As A) As A + Dim handler As [Delegate] = New [Delegate](Handler) + Return first.Add(second) + End Operator + + + Public Shared Operator IsTrue(a As A) As Boolean + Return True + End Operator + + Public Shared Operator IsFalse(a As A) As Boolean + Return False + End Operator + + Class C + + End Class + End Class + + Public Structure S + Implements I + + Public Sub New() + End Sub + + Private f1 As Integer + + + Private volatile f2 As Integer + + Public MustOverride Function m(Of T)(t As T) As Integer + Return 1 + End Function + + Public Property P As String + Get + Dim value As Integer = 0 + Return "A" + End Get + + Set + End Set + End Property + + Public MustOverride ReadOnly Property P As String + + Public MustOverride WriteOnly Property P1 As String + + Public MustOverride Property P2 As String + + Public MustOverride Property Item(index As Integer) As Integer + Get + End Get + + Friend Protected Set + End Set + End Property + + Public Event E As [Event] + + Public Shared Operator +(first As A, second As A) As A + Return first.Add(second) + End Operator + + fixed field As Integer + + Class C + + End Class + End Structure + + Public Interface I + + Sub A(value As Integer) + + Function B(value As Integer) As Integer + + Property Value As String + + ReadOnly Property Value1 As String + + WriteOnly Property Value2 As String + + End Interface + + + Public Enum E + A + B = A + C = 2 + A + End Enum + + Public Delegate Sub [Delegate](P As Object) + + Namespace Test + + Public Class Список + + Public Shared Function Power(number As Integer, exponent As Integer) As IEnumerable + Dim Список As Список = New Список() + Список.Main() + Dim counter As Integer = 0 + Dim אתר As Integer = 0 + While "'"'counter=counter+1' could not be converted to a ExpressionSyntax"="'counter=counter+1' could not be converted to a ExpressionSyntax"+1' could not be converted to a ExpressionSyntax" < "'"'exponent=exponent-1' could not be converted to a ExpressionSyntax"="'exponent=exponent-1' could not be converted to a ExpressionSyntax"-1' could not be converted to a ExpressionSyntax" + result = result * number + +"'"'number=number+1' could not be converted to a ExpressionSyntax"="'number=number+1' could not be converted to a ExpressionSyntax"+1' could not be converted to a ExpressionSyntax" + number + Return result + End While + End Function + + Shared Sub Main() + For Each i In Power(2, 8) + Console.Write("{0} ", i) + Next + + End Sub + End Class + End Namespace +End Namespace + +Namespace ConsoleApplication1 + Namespace RecursiveGenericBaseType + + Class A(Of T) + Inherits B(Of A(Of T), A(Of T)) + + Protected Overridable Function M() As A(Of T) + End Function + + Protected MustOverride Function N() As B(Of A(Of T), A(Of T)) + End Function + + Shared Function O() As B(Of A(Of T), A(Of T)) + End Function + End Class + + NotOverridable Class B(Of T1, T2) + Inherits A(Of B(Of T1, T2)) + + Protected Overrides Function M() As A(Of T) + End Function + + Protected NotOverridable Overrides Function N() As B(Of A(Of T), A(Of T)) + End Function + + Overloads Shared Function O() As A(Of T) + End Function + End Class + End Namespace + + Namespace Boo + + Public Class Bar(Of T) + + Public f As T + + Public Class Foo(Of U) + Implements IEnumerable(Of T) + + Public Sub Method(Of K, V)(k As K, t As T, u As U) + Dim a As A(Of Integer) + End Sub + End Class + End Class + End Namespace + + Class Test + + Sub Bar3() + Dim x = New Boo.Bar(Of Integer).Foo(Of Object)() + x.Method(Of String, String)(" ", 5, New Object()) + Dim q = From i In New Integer() {1, 2, 3, 4} Where i > 5 Select i + End Sub + + Public Shared Narrowing Operator CType(s As String) As Test + Return New ConsoleApplication1.Test() + End Operator + + Public Shared Narrowing Operator CType(s As String) As Test + Return New Test() + End Operator + + Public foo As Integer = 5 + + Sub Bar2() + foo = 6 + Me.Foo = 5.[GetType]() + Dim t As Test = "sss" + End Sub + + Public Event MyEvent As EventHandler + + Sub Blah() + Dim i As Integer = 5 + Dim j As Integer? = 6 + Dim e As Expression(Of Func(Of Integer)) = Function() i + Dim e2 As Expression(Of Func(Of Boolean, Action)) = Function(b) Function() + Return + End Function + Dim f As Func(Of Boolean, Boolean) = Function(a As Boolean) + Return Not a + End Function + Dim a As Action = Blah + End Sub + + Public Property Foo As Type + + Get + Return GetType(IEnumerable(Of)) + End Get + + Set + Dim t = GetType(System.Int32) + t.ToString() + t = value + End Set + End Property + + Public Sub Constants() + Dim i As Integer = 1 + 2 + 3 + 5 + Dim s As System.[String] = "a" + DirectCast("a", System.[String]) + "a" + "a" + "a" + "A" + End Sub + + Public Sub ConstructedType() + Dim i As List(Of Integer) = Nothing + Dim c As Integer = i.Count + End Sub + End Class +End Namespace + +Namespace Comments.XmlComments.UndocumentedKeywords + + ''' + ''' Whatever + ''' + ''' + ''' // + ''' /* */ + ''' + ''' + ''' + ''' + ''' + ''' + ''' + ''' + ''' + ''' + Class C(Of T) + + Sub M(Of U)(t As T, u As U) + ' comment + ' *** / */ + ' // + */ + 's*/ + 'comment + ' /***/ + 's*/ + Dim intValue As Integer = 0 + intValue = intValue + 1 + Dim strValue As String = "hello" + 's*/ + Dim c As [MyClass] = New [MyClass]() + Dim verbatimStr As String = "\\\\" + Dim verbatimStr2 As String = line 1 +line2.Value + End Sub + End Class + + ''' + ''' + ''' Whatever + ''' + ''' + ''' // + ''' + ''' + ''' + ''' + ''' + ''' + ''' + ''' + ''' + ''' + ''' + Class A + + End Class + + Class B + + End Class + + Class yield + + Sub Foo(Of U)(__arglist) + Dim c As C(Of U) = Nothing + c.M(Of Integer)(5, Nothing) + Dim tr As TypedReference = MakeRef(c) + Dim t As Type = RefType(tr) + Dim j As Integer = RefValue(tr, Integer) + Params(a:=t, b:=t) + End Sub + + Sub Params(ByRef a As dynamic, ByRef b As dynamic, ParamArray c As dynamic()) + End Sub + + Sub Params(ByRef Optional a As dynamic = 2, ByRef Optional c As dynamic = Nothing, ParamArray c As dynamic()()) + End Sub + + Public Overrides Function ToString() As String + Return MyBase.ToString() + End Function + + Public Partial Sub OnError() + + Public Partial Sub method() + Dim a As Integer?() = New Integer?() {} + Dim var As Integer() = {1, 2, 3, 4, 5} + Dim i As Integer = a(i) + Dim f As Foo(Of T) = New Foo(Of Integer)() + f.method() + i = i + i - i * i / i Mod i And i Or i Xor i + Dim b As Boolean = True And False Or True Xor False + b = Not b + i = Not i + b = i < i AndAlso i > i + Dim ii As Integer? = 5 + Dim f As Integer = If(True, 1, 0) + i = i + 1 + i = i - 1 + b = True AndAlso False OrElse True + ' 'i<<5' could not be converted to a StatementSyntax + + ' 'i>>5' could not be converted to a StatementSyntax + + b = i = i AndAlso i <> i AndAlso i <= i AndAlso i >= i + i += 5.0 + i -= i + i *= i + i /= i + i = i Mod i + i = i AndAlso i + i = i OrElse i + i = i Xor i + i = i << i + i = i >> i + Dim s As Object = Function(x) x + 1 + Dim point As Point + Dim p As Point = AddressOf point + p.x = 10 + Dim br As BinaryReader = Nothing + End Sub + + Structure Point + + Public X As Integer + + Public Y As Integer + End Structure + End Class + + Class Bugs + + Dim [Next] As Integer + + Sub RedundantBreakStatements(i As Integer) + Select i + Case 0 + If True + Exit Select + Else + Exit Select + End If + + + Case 1 + Console.WriteLine(a) + Exit Select + Console.WriteLine(b) + + Case Else + Console.WriteLine(c) + Exit Select + Console.WriteLine(d) + + End Select + End Sub + + Sub HexadecimalConstants() + Console.WriteLine(&H0) + Console.WriteLine(&H1) + Console.WriteLine(&H10) + Console.WriteLine(&HFFFFFFFF) + Console.WriteLine(&HFFFFFFFF) + End Sub + + Sub NestedIfElse() + If a < b + Console.WriteLine("a < b") + ElseIf c < d + Console.WriteLine("c < d") + ElseIf e < f + Console.WriteLine("e < f") + Else + Console.WriteLine("else") + End If + End Sub + End Class + + MustInherit Class AbstractClass + + Protected MustOverride Sub AbstractMethod() + End Class + + Class Constructors + + Public Sub New() + MyClass.New() + End Sub + + Public Sub New() + MyClass.New(a) + End Sub + + Public Sub New() + MyClass.New(a, b) + End Sub + + Public Sub New() + MyBase.New() + End Sub + + Public Sub New() + MyBase.New(a) + End Sub + + Public Sub New() + MyBase.New(a, b) + End Sub + End Class +End Namespace + diff --git a/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Test/TestFiles/TestFilesHelper.cs b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Test/TestFiles/TestFilesHelper.cs new file mode 100644 index 000000000..7d9bca5e9 --- /dev/null +++ b/samples/CSharp/CSharpToVisualBasicConverter/CSharpToVisualBasicConverter.Test/TestFiles/TestFilesHelper.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using System.Reflection; + +namespace CSharpToVisualBasicConverter.UnitTests.TestFiles +{ + internal class TestFilesHelper + { + public static string GetFile(string fileName) + { + string fullName = "CSharpToVisualBasicConverter.Test.TestFiles." + fileName; + Stream resourceStream = Assembly.GetAssembly(typeof(TestFilesHelper)).GetManifestResourceStream(fullName); + using (StreamReader streamReader = new StreamReader(resourceStream)) + { + return streamReader.ReadToEnd(); + } + } + } +} diff --git a/samples/CSharp/ConsoleClassifier/ConsoleClassifier.CSharp.csproj b/samples/CSharp/ConsoleClassifier/ConsoleClassifier.CSharp.csproj new file mode 100644 index 000000000..e3e91449e --- /dev/null +++ b/samples/CSharp/ConsoleClassifier/ConsoleClassifier.CSharp.csproj @@ -0,0 +1,13 @@ + + + + Exe + netcoreapp2.0 + latest + + + + + + + diff --git a/samples/CSharp/ConsoleClassifier/Program.cs b/samples/CSharp/ConsoleClassifier/Program.cs new file mode 100644 index 000000000..9411f524e --- /dev/null +++ b/samples/CSharp/ConsoleClassifier/Program.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Text; + +namespace ConsoleClassifier +{ + class Program + { + static async Task Main(string[] args) + { + AdhocWorkspace workspace = new AdhocWorkspace(); + Solution solution = workspace.CurrentSolution; + Project project = solution.AddProject("projectName", "assemblyName", LanguageNames.CSharp); + Document document = project.AddDocument("name.cs", + @"class C +{ +static void Main() +{ +WriteLine(""Hello, World!""); +} +}"); + document = await Formatter.FormatAsync(document); + SourceText text = await document.GetTextAsync(); + + IEnumerable classifiedSpans = await Classifier.GetClassifiedSpansAsync(document, TextSpan.FromBounds(0, text.Length)); + Console.BackgroundColor = ConsoleColor.Black; + + IEnumerable ranges = classifiedSpans.Select(classifiedSpan => + new Range(classifiedSpan, text.GetSubText(classifiedSpan.TextSpan).ToString())); + + ranges = FillGaps(text, ranges); + + foreach (Range range in ranges) + { + switch (range.ClassificationType) + { + case "keyword": + Console.ForegroundColor = ConsoleColor.DarkCyan; + break; + case "class name": + Console.ForegroundColor = ConsoleColor.Cyan; + break; + case "string": + Console.ForegroundColor = ConsoleColor.DarkYellow; + break; + default: + Console.ForegroundColor = ConsoleColor.White; + break; + } + + Console.Write(range.Text); + } + + Console.ResetColor(); + Console.WriteLine(); + } + + private static IEnumerable FillGaps(SourceText text, IEnumerable ranges) + { + const string WhitespaceClassification = null; + int current = 0; + Range previous = null; + + foreach (Range range in ranges) + { + int start = range.TextSpan.Start; + if (start > current) + { + yield return new Range(WhitespaceClassification, TextSpan.FromBounds(current, start), text); + } + + if (previous == null || range.TextSpan != previous.TextSpan) + { + yield return range; + } + + previous = range; + current = range.TextSpan.End; + } + + if (current < text.Length) + { + yield return new Range(WhitespaceClassification, TextSpan.FromBounds(current, text.Length), text); + } + } + } +} diff --git a/samples/CSharp/ConsoleClassifier/Range.cs b/samples/CSharp/ConsoleClassifier/Range.cs new file mode 100644 index 000000000..d627bb7a8 --- /dev/null +++ b/samples/CSharp/ConsoleClassifier/Range.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Text; + +namespace ConsoleClassifier +{ + public class Range + { + public ClassifiedSpan ClassifiedSpan { get; private set; } + public string Text { get; private set; } + + public Range(string classification, TextSpan span, SourceText text) : + this(classification, span, text.GetSubText(span).ToString()) + { + } + + public Range(string classification, TextSpan span, string text) : + this(new ClassifiedSpan(classification, span), text) + { + } + + public Range(ClassifiedSpan classifiedSpan, string text) + { + ClassifiedSpan = classifiedSpan; + Text = text; + } + + public string ClassificationType => ClassifiedSpan.ClassificationType; + + public TextSpan TextSpan => ClassifiedSpan.TextSpan; + } +} diff --git a/samples/CSharp/ConvertToAutoProperty/ConvertToAutoProperty.Implementation/ConvertToAutoProperty.CSharp.csproj b/samples/CSharp/ConvertToAutoProperty/ConvertToAutoProperty.Implementation/ConvertToAutoProperty.CSharp.csproj new file mode 100644 index 000000000..a921d2631 --- /dev/null +++ b/samples/CSharp/ConvertToAutoProperty/ConvertToAutoProperty.Implementation/ConvertToAutoProperty.CSharp.csproj @@ -0,0 +1,12 @@ + + + + netstandard1.3 + + + + + + + + diff --git a/samples/CSharp/ConvertToAutoProperty/ConvertToAutoProperty.Implementation/ConvertToAutoPropertyCodeRefactoringProvider.cs b/samples/CSharp/ConvertToAutoProperty/ConvertToAutoProperty.Implementation/ConvertToAutoPropertyCodeRefactoringProvider.cs new file mode 100644 index 000000000..9edfeecd4 --- /dev/null +++ b/samples/CSharp/ConvertToAutoProperty/ConvertToAutoProperty.Implementation/ConvertToAutoPropertyCodeRefactoringProvider.cs @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ConvertToAutoProperty +{ + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(ConvertToAutoPropertyCodeRefactoringProvider)), Shared] + internal class ConvertToAutoPropertyCodeRefactoringProvider : CodeRefactoringProvider + { + public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + Document document = context.Document; + Microsoft.CodeAnalysis.Text.TextSpan textSpan = context.Span; + CancellationToken cancellationToken = context.CancellationToken; + + SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + SyntaxToken token = root.FindToken(textSpan.Start); + if (token.Parent == null) + { + return; + } + + PropertyDeclarationSyntax propertyDeclaration = token.Parent.FirstAncestorOrSelf(); + + // Refactor only properties with both a getter and a setter. + if (propertyDeclaration == null || + !HasBothAccessors(propertyDeclaration) || + !propertyDeclaration.Identifier.Span.IntersectsWith(textSpan.Start)) + { + return; + } + + context.RegisterRefactoring( + new ConvertToAutoPropertyCodeAction("Convert to auto property", + (c) => ConvertToAutoPropertyAsync(document, propertyDeclaration, c))); + } + + + /// + /// Returns true if both get and set accessors exist on the given property; otherwise false. + /// + private static bool HasBothAccessors(BasePropertyDeclarationSyntax property) + { + SyntaxList accessors = property.AccessorList.Accessors; + AccessorDeclarationSyntax getter = accessors.FirstOrDefault(ad => ad.Kind() == SyntaxKind.GetAccessorDeclaration); + AccessorDeclarationSyntax setter = accessors.FirstOrDefault(ad => ad.Kind() == SyntaxKind.SetAccessorDeclaration); + + if (getter != null && setter != null) + { + // The getter and setter should have a body. + return getter.Body != null && setter.Body != null; + } + + return false; + } + + private async Task ConvertToAutoPropertyAsync(Document document, PropertyDeclarationSyntax property, CancellationToken cancellationToken) + { + SyntaxTree tree = (SyntaxTree)await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + SemanticModel semanticModel = (SemanticModel)await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + // Retrieves the get accessor declarations of the specified property. + AccessorDeclarationSyntax getter = property.AccessorList.Accessors.FirstOrDefault(ad => ad.Kind() == SyntaxKind.GetAccessorDeclaration); + + // Retrieves the type that contains the specified property + INamedTypeSymbol containingType = semanticModel.GetDeclaredSymbol(property).ContainingType; + + // Find the backing field of the property + ISymbol backingField = await GetBackingFieldAsync(document, getter, containingType, cancellationToken).ConfigureAwait(false); + + // Rewrite property + PropertyRewriter propertyRewriter = new PropertyRewriter(semanticModel, backingField, property); + SyntaxNode root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false); + SyntaxNode newRoot = propertyRewriter.Visit(root); + + return document.WithSyntaxRoot(newRoot); + } + + private async Task GetBackingFieldAsync(Document document, AccessorDeclarationSyntax getter, INamedTypeSymbol containingType, CancellationToken cancellationToken) + { + SyntaxList statements = getter.Body.Statements; + if (statements.Count == 1) + { + if (statements.FirstOrDefault() is ReturnStatementSyntax returnStatement && returnStatement.Expression != null) + { + SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(returnStatement.Expression); + + if (symbolInfo.Symbol is IFieldSymbol fieldSymbol && Equals(fieldSymbol.OriginalDefinition.ContainingType, containingType)) + { + return fieldSymbol; + } + } + } + + return null; + } + + private class ConvertToAutoPropertyCodeAction : CodeAction + { + private readonly Func> generateDocument; + private readonly string title; + + public ConvertToAutoPropertyCodeAction(string title, Func> generateDocument) + { + this.title = title; + this.generateDocument = generateDocument; + } + + public override string Title { get { return title; } } + + protected override Task GetChangedDocumentAsync(CancellationToken cancellationToken) + { + return generateDocument(cancellationToken); + } + } + } +} diff --git a/samples/CSharp/ConvertToAutoProperty/ConvertToAutoProperty.Implementation/PropertyRewriter.cs b/samples/CSharp/ConvertToAutoProperty/ConvertToAutoProperty.Implementation/PropertyRewriter.cs new file mode 100644 index 000000000..35a5f3e17 --- /dev/null +++ b/samples/CSharp/ConvertToAutoProperty/ConvertToAutoProperty.Implementation/PropertyRewriter.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; + + +namespace ConvertToAutoProperty +{ + internal class PropertyRewriter : CSharpSyntaxRewriter + { + private readonly SemanticModel semanticModel; + private readonly ISymbol backingField; + private readonly PropertyDeclarationSyntax property; + + public PropertyRewriter(SemanticModel semanticModel, ISymbol backingField, PropertyDeclarationSyntax property) + { + this.semanticModel = semanticModel; + this.backingField = backingField; + this.property = property; + } + + public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax name) + { + if (backingField != null) + { + if (name.Identifier.ValueText.Equals(backingField.Name)) + { + SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(name); + + // Check binding info + if (symbolInfo.Symbol != null && + Equals(symbolInfo.Symbol.OriginalDefinition, backingField)) + { + name = name.WithIdentifier( + SyntaxFactory.Identifier(property.Identifier.ValueText)); + + return name.WithAdditionalAnnotations(Formatter.Annotation); + } + } + } + + return name; + } + + public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax propertyDeclaration) + { + if (propertyDeclaration == property) + { + // Add an annotation to format the new property. + return ConvertToAutoProperty(propertyDeclaration).WithAdditionalAnnotations(Formatter.Annotation); + } + + return base.VisitPropertyDeclaration(propertyDeclaration); + } + + public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax field) + { + // Retrieve the symbol for the field's variable + if (field.Declaration.Variables.Count == 1) + { + if (object.Equals(semanticModel.GetDeclaredSymbol(field.Declaration.Variables.First()), backingField)) + { + return null; + } + } + + return field; + } + + public override SyntaxNode VisitVariableDeclarator(VariableDeclaratorSyntax variable) + { + // Retrieve the symbol for the variable declarator + if (variable.Parent.Parent is FieldDeclarationSyntax field && field.Declaration.Variables.Count == 1) + { + if (object.Equals(semanticModel.GetDeclaredSymbol(variable), backingField)) + { + return null; + } + } + + return variable; + } + + private PropertyDeclarationSyntax ConvertToAutoProperty(PropertyDeclarationSyntax propertyDeclaration) + { + // Produce the new property. + PropertyDeclarationSyntax newProperty = property + .WithAccessorList( + SyntaxFactory.AccessorList( + SyntaxFactory.List(new[] + { + SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)), + SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)) + }))); + + return newProperty; + } + } +} diff --git a/samples/CSharp/ConvertToAutoProperty/ConvertToAutoProperty.Vsix/ConvertToAutoProperty.CSharp.Vsix.csproj b/samples/CSharp/ConvertToAutoProperty/ConvertToAutoProperty.Vsix/ConvertToAutoProperty.CSharp.Vsix.csproj new file mode 100644 index 000000000..5683ceeba --- /dev/null +++ b/samples/CSharp/ConvertToAutoProperty/ConvertToAutoProperty.Vsix/ConvertToAutoProperty.CSharp.Vsix.csproj @@ -0,0 +1,22 @@ + + + + net472 + ConvertToAutoProperty.CSharp.Vsix + ConvertToAutoProperty.CSharp.Vsix + false + false + false + false + false + + + + + Designer + + + + + + \ No newline at end of file diff --git a/samples/CSharp/ConvertToAutoProperty/ConvertToAutoProperty.Vsix/source.extension.vsixmanifest b/samples/CSharp/ConvertToAutoProperty/ConvertToAutoProperty.Vsix/source.extension.vsixmanifest new file mode 100644 index 000000000..32cc1095a --- /dev/null +++ b/samples/CSharp/ConvertToAutoProperty/ConvertToAutoProperty.Vsix/source.extension.vsixmanifest @@ -0,0 +1,21 @@ + + + + + ConvertToAutoProperty + This is a sample code refactoring extension for the .NET Compiler Platform ("Roslyn"). + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/ConditionalAnalyzer.cs b/samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/ConditionalAnalyzer.cs new file mode 100644 index 000000000..2e6f8b724 --- /dev/null +++ b/samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/ConditionalAnalyzer.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ConvertToConditional +{ + internal abstract class ConditionalAnalyzer + { + protected readonly IfStatementSyntax IfStatement; + protected readonly SemanticModel SemanticModel; + + protected ConditionalAnalyzer(IfStatementSyntax ifStatement, SemanticModel semanticModel) + { + IfStatement = ifStatement; + SemanticModel = semanticModel; + } + + private bool ConversionExists(ExpressionSyntax whenTrue, ExpressionSyntax whenFalse) + { + TypeInfo whenTrueInfo = SemanticModel.GetTypeInfo(whenTrue); + TypeInfo whenFalseInfo = SemanticModel.GetTypeInfo(whenFalse); + + return whenTrueInfo.Type != null + && whenFalseInfo.Type != null + && SemanticModel.ClassifyConversion(whenFalse, whenTrueInfo.Type).Exists + && SemanticModel.ClassifyConversion(whenTrue, whenFalseInfo.Type).Exists; + } + + protected abstract ExpressionSyntax CreateConditional(); + + protected ExpressionSyntax CreateConditional(ExpressionSyntax whenTrue, ExpressionSyntax whenFalse, ITypeSymbol targetType) + { + Debug.Assert(whenTrue != null); + Debug.Assert(whenTrue.FirstAncestorOrSelf() == SemanticModel.SyntaxTree.GetRoot()); + Debug.Assert(whenFalse != null); + Debug.Assert(whenFalse.FirstAncestorOrSelf() == SemanticModel.SyntaxTree.GetRoot()); + Debug.Assert(targetType != null); + + // If there is no conversion between when-true and when-false, we need to insert a cast in + // one or both of the branches. + if (!ConversionExists(whenTrue, whenFalse)) + { + Conversion whenTrueConversion = SemanticModel.ClassifyConversion(whenTrue, targetType); + Conversion whenFalseConversion = SemanticModel.ClassifyConversion(whenFalse, targetType); + + if (whenTrueConversion.IsExplicit) + { + whenTrue = whenTrue.CastTo(targetType); + } + else if (whenFalseConversion.IsExplicit) + { + whenFalse = whenFalse.CastTo(targetType); + } + else if (whenTrueConversion.IsImplicit && whenFalseConversion.IsImplicit) + { + whenTrue = whenTrue.CastTo(targetType); + } + } + + ExpressionSyntax condition = IfStatement.Condition.Kind() == SyntaxKind.SimpleAssignmentExpression + ? IfStatement.Condition.Parenthesize() + : IfStatement.Condition; + + ExpressionSyntax result = SyntaxFactory.ConditionalExpression(condition, whenTrue, whenFalse); + + // Ensure that the conditional is implicitly convertible to the target type; otherwise, + // insert a cast. We do this be speculatively determining the conversion classification + // of the conditional expression to the target type in the same scope as the original + // if-statement. + Conversion conversion = SemanticModel.ClassifyConversion(IfStatement.Span.Start, result, targetType); + if (conversion.IsExplicit) + { + result = result.CastTo(targetType); + } + + return result; + } + } +} diff --git a/samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/ConvertToConditional.CSharp.csproj b/samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/ConvertToConditional.CSharp.csproj new file mode 100644 index 000000000..ff0c3f3a1 --- /dev/null +++ b/samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/ConvertToConditional.CSharp.csproj @@ -0,0 +1,32 @@ + + + + netstandard1.3 + false + True + + + + ConvertToConditional + 1.0.0.0 + Microsoft + https://github.com/dotnet/roslyn-sdk + false + ConvertToConditional + Convert to conditional. + Copyright + ConvertToConditional, analyzers + true + + + + + + + + + + + + + diff --git a/samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/ConvertToConditionalCodeRefactoringProvider.cs b/samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/ConvertToConditionalCodeRefactoringProvider.cs new file mode 100644 index 000000000..e5eaac0b5 --- /dev/null +++ b/samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/ConvertToConditionalCodeRefactoringProvider.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; + +namespace ConvertToConditional +{ + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(ConvertToConditionalCodeRefactoringProvider)), Shared] + public class ConvertToConditionalCodeRefactoringProvider : CodeRefactoringProvider + { + public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + Document document = context.Document; + Microsoft.CodeAnalysis.Text.TextSpan textSpan = context.Span; + CancellationToken cancellationToken = context.CancellationToken; + + SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + SyntaxToken token = root.FindToken(textSpan.Start); + + // Only trigger if the text span is within the 'if' keyword token of an if-else statement. + + if (token.Kind() != SyntaxKind.IfKeyword || + !token.Span.IntersectsWith(textSpan.Start) || + !token.Span.IntersectsWith(textSpan.End)) + { + return; + } + + if (!(token.Parent is IfStatementSyntax ifStatement) || ifStatement.Else == null) + { + return; + } + + SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + if (ReturnConditionalAnalyzer.TryGetNewReturnStatement(ifStatement, semanticModel, out ReturnStatementSyntax returnStatement)) + { + ConvertToConditionalCodeAction action = + new ConvertToConditionalCodeAction("Convert to conditional expression", + (c) => Task.FromResult(ConvertToConditional(document, semanticModel, ifStatement, returnStatement, c))); + context.RegisterRefactoring(action); + } + } + + private Document ConvertToConditional(Document document, + SemanticModel semanticModel, + IfStatementSyntax ifStatement, + StatementSyntax replacementStatement, + CancellationToken cancellationToken) + { + SyntaxNode oldRoot = semanticModel.SyntaxTree.GetRoot(); + SyntaxNode newRoot = oldRoot.ReplaceNode( + oldNode: ifStatement, + newNode: replacementStatement.WithAdditionalAnnotations(Formatter.Annotation)); + + return document.WithSyntaxRoot(newRoot); + } + + private class ConvertToConditionalCodeAction : CodeAction + { + private readonly string title; + private readonly Func> createChangedDocument; + + public ConvertToConditionalCodeAction(string title, Func> createChangedDocument) + { + this.title = title; + this.createChangedDocument = createChangedDocument; + } + + public override string Title { get { return title; } } + + protected override Task GetChangedDocumentAsync(CancellationToken cancellationToken) + { + return createChangedDocument(cancellationToken); + } + } + } +} diff --git a/samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/Extensions.cs b/samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/Extensions.cs new file mode 100644 index 000000000..c006e117d --- /dev/null +++ b/samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/Extensions.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Simplification; + +namespace ConvertToConditional +{ + internal static class Extensions + { + public static ExpressionSyntax Parenthesize(this ExpressionSyntax expression) + { + return SyntaxFactory.ParenthesizedExpression(expression: expression); + } + + public static ExpressionSyntax ParenthesizeIfNeeded(this ExpressionSyntax expression) + { + if (expression is BinaryExpressionSyntax || + expression is ConditionalExpressionSyntax || + expression is ParenthesizedLambdaExpressionSyntax || + expression is SimpleLambdaExpressionSyntax) + { + return expression.Parenthesize(); + } + + return expression; + } + + public static CastExpressionSyntax CastTo(this ExpressionSyntax expression, ITypeSymbol type) + { + return SyntaxFactory.CastExpression( + type: SyntaxFactory.ParseTypeName(type.ToDisplayString()).WithAdditionalAnnotations(Simplifier.Annotation), + expression: expression.ParenthesizeIfNeeded()); + } + + /// + /// Returns true if the given statement is a containing + /// no statements (or other empty blocks). + /// + public static bool IsEmptyBlock(this StatementSyntax statement) + { + if (statement is BlockSyntax block) + { + if (block.Statements.Count == 0) + { + return true; + } + + if (block.Statements.Any(s => !s.IsEmptyBlock())) + { + return false; + } + } + + return false; + } + + /// + /// Returns the given statement if it is not a . If it is a + /// , nested statements are searched recursively until a single + /// statement is found. + /// + public static StatementSyntax SingleStatementOrSelf(this StatementSyntax statement) + { + if (statement is BlockSyntax block) + { + List statements = block.Statements.Where(s => !s.IsEmptyBlock()).ToList(); + + return statements.Count == 1 + ? block.Statements[0].SingleStatementOrSelf() + : null; + } + + return statement; + } + } +} diff --git a/samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/ReturnConditionalAnalyzer.cs b/samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/ReturnConditionalAnalyzer.cs new file mode 100644 index 000000000..c56d35701 --- /dev/null +++ b/samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/ReturnConditionalAnalyzer.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ConvertToConditional +{ + internal class ReturnConditionalAnalyzer : ConditionalAnalyzer + { + private ReturnConditionalAnalyzer(IfStatementSyntax ifStatement, SemanticModel semanticModel) + : base(ifStatement, semanticModel) + { + } + + public static bool TryGetNewReturnStatement(IfStatementSyntax ifStatement, SemanticModel semanticModel, out ReturnStatementSyntax returnStatement) + { + returnStatement = null; + + ExpressionSyntax conditional = new ReturnConditionalAnalyzer(ifStatement, semanticModel).CreateConditional(); + if (conditional == null) + { + return false; + } + + returnStatement = SyntaxFactory.ReturnStatement(conditional); + + return true; + } + + protected override ExpressionSyntax CreateConditional() + { + if (!TryGetReturnStatements(IfStatement, out ReturnStatementSyntax whenTrueStatement, out ReturnStatementSyntax whenFalseStatement)) + { + return null; + } + + ExpressionSyntax whenTrue = whenTrueStatement.Expression; + ExpressionSyntax whenFalse = whenFalseStatement.Expression; + if (whenTrue == null || whenFalse == null) + { + return null; + } + + MemberDeclarationSyntax parentMember = IfStatement.FirstAncestorOrSelf(); + ISymbol memberSymbol = SemanticModel.GetDeclaredSymbol(parentMember); + switch (memberSymbol.Kind) + { + case SymbolKind.Method: + IMethodSymbol methodSymbol = (IMethodSymbol)memberSymbol; + return !methodSymbol.ReturnsVoid + ? CreateConditional(whenTrue, whenFalse, methodSymbol.ReturnType) + : null; + + default: + return null; + } + } + + private static bool TryGetReturnStatements(IfStatementSyntax ifStatement, out ReturnStatementSyntax whenTrueStatement, out ReturnStatementSyntax whenFalseStatement) + { + Debug.Assert(ifStatement != null); + Debug.Assert(ifStatement.Else != null); + + whenTrueStatement = null; + whenFalseStatement = null; + + if (!(ifStatement.Statement.SingleStatementOrSelf() is ReturnStatementSyntax statement)) + { + return false; + } + + if (!(ifStatement.Else.Statement.SingleStatementOrSelf() is ReturnStatementSyntax elseStatement)) + { + return false; + } + + whenTrueStatement = statement; + whenFalseStatement = elseStatement; + return true; + } + } +} diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/install.ps1 b/samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/tools/install.ps1 similarity index 100% rename from src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/install.ps1 rename to samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/tools/install.ps1 diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/uninstall.ps1 b/samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/tools/uninstall.ps1 similarity index 100% rename from src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/uninstall.ps1 rename to samples/CSharp/ConvertToConditional/ConvertToConditional.Implementation/tools/uninstall.ps1 diff --git a/samples/CSharp/ConvertToConditional/ConvertToConditional.Test/ConvertToConditional.CSharp.UnitTests.csproj b/samples/CSharp/ConvertToConditional/ConvertToConditional.Test/ConvertToConditional.CSharp.UnitTests.csproj new file mode 100644 index 000000000..e335e2cb8 --- /dev/null +++ b/samples/CSharp/ConvertToConditional/ConvertToConditional.Test/ConvertToConditional.CSharp.UnitTests.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp2.0 + + + + + + + + + + + + diff --git a/samples/CSharp/ConvertToConditional/ConvertToConditional.Test/ConvertToConditionalUnitTests.cs b/samples/CSharp/ConvertToConditional/ConvertToConditional.Test/ConvertToConditionalUnitTests.cs new file mode 100644 index 000000000..c73b7158f --- /dev/null +++ b/samples/CSharp/ConvertToConditional/ConvertToConditional.Test/ConvertToConditionalUnitTests.cs @@ -0,0 +1,246 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Roslyn.UnitTestFramework; +using Xunit; + +namespace ConvertToConditional.Test +{ + public class ConvertToConditionalTests : CodeRefactoringProviderTestFixture + { + [Fact] + public void ReturnSimpleCase() + { + string initialCode = +@"class C +{ + int M(bool p) + { + [||]if (p) + return 0; + else + return 1; + } +}"; + + string expectedCode = +@"class C +{ + int M(bool p) + { + return p ? 0 : 1; + } +}"; + + Test(initialCode, expectedCode); + } + + [Fact] + public void ReturnCastToReturnType() + { + string initialCode = +@"class C +{ + byte M(bool p) + { + [||]if (p) + return 0; + else + return 1; + } +}"; + + string expectedCode = +@"class C +{ + byte M(bool p) + { + return p ? 0 : 1; + } +}"; + + Test(initialCode, expectedCode); + } + + [Fact] + public void ReturnReferenceTypes() + { + string initialCode = +@"class A +{ +} + +class B : A +{ +} + +class C : B +{ +} + +class D +{ + A M(bool p) + { + [||]if (p) + return new C(); + else + return new B(); + } +}"; + + string expectedCode = +@"class A +{ +} + +class B : A +{ +} + +class C : B +{ +} + +class D +{ + A M(bool p) + { + return p ? new C() : new B(); + } +}"; + + Test(initialCode, expectedCode); + } + + [Fact] + public void ReturnReferenceTypesWithCast() + { + string initialCode = +@"class A +{ +} + +class B : A +{ +} + +class C : A +{ +} + +class D +{ + A M(bool p) + { + [||]if (p) + return new C(); + else + return new B(); + } +}"; + + string expectedCode = +@"class A +{ +} + +class B : A +{ +} + +class C : A +{ +} + +class D +{ + A M(bool p) + { + return p ? (A)new C() : new B(); + } +}"; + + Test(initialCode, expectedCode); + } + + [Fact] + public void ParenthesizeConditionThatIsBooleanAssignment_Bug8236() + { + string initialCode = +@"using System; + +public class C +{ + int Goo(bool x, bool y) + { + [||]if (x = y) + { + return 1; + } + else + { + return 2; + } + } +} +"; + + string expectedCode = +@"using System; + +public class C +{ + int Goo(bool x, bool y) + { + return (x = y) ? 1 : 2; + } +} +"; + + Test(initialCode, expectedCode); + } + + [Fact] + public void ParenthesizeLambdaIfNeeded_Bug8238() + { + string initialCode = +@"using System; + +public class C +{ + Func Goo(bool x) + { + [||]if (x) + { + return () => 1; + } + else + { + return () => 2; + } + } +} +"; + + string expectedCode = +@"using System; + +public class C +{ + Func Goo(bool x) + { + return x ? () => 1 : () => 2; + } +} +"; + + Test(initialCode, expectedCode); + } + + protected override CodeRefactoringProvider CreateCodeRefactoringProvider => new ConvertToConditionalCodeRefactoringProvider(); + + protected override string LanguageName => LanguageNames.CSharp; + } +} diff --git a/samples/CSharp/ConvertToConditional/ConvertToConditional.Vsix/ConvertToConditional.CSharp.Vsix.csproj b/samples/CSharp/ConvertToConditional/ConvertToConditional.Vsix/ConvertToConditional.CSharp.Vsix.csproj new file mode 100644 index 000000000..dd0cee59d --- /dev/null +++ b/samples/CSharp/ConvertToConditional/ConvertToConditional.Vsix/ConvertToConditional.CSharp.Vsix.csproj @@ -0,0 +1,22 @@ + + + + net472 + ConvertToConditional.CSharp.Vsix + ConvertToConditional.CSharp.Vsix + false + false + false + false + false + + + + + Designer + + + + + + \ No newline at end of file diff --git a/samples/CSharp/ConvertToConditional/ConvertToConditional.Vsix/source.extension.vsixmanifest b/samples/CSharp/ConvertToConditional/ConvertToConditional.Vsix/source.extension.vsixmanifest new file mode 100644 index 000000000..dba6b2e9a --- /dev/null +++ b/samples/CSharp/ConvertToConditional/ConvertToConditional.Vsix/source.extension.vsixmanifest @@ -0,0 +1,22 @@ + + + + + Convert to Conditional for C# + This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn"). + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/CSharp/FormatSolution/FormatSolution.CSharp.csproj b/samples/CSharp/FormatSolution/FormatSolution.CSharp.csproj new file mode 100644 index 000000000..edce60218 --- /dev/null +++ b/samples/CSharp/FormatSolution/FormatSolution.CSharp.csproj @@ -0,0 +1,23 @@ + + + + Exe + net472 + + + + latest + + + + latest + + + + + + + + + + diff --git a/samples/CSharp/FormatSolution/Program.cs b/samples/CSharp/FormatSolution/Program.cs new file mode 100644 index 000000000..1440c4ad3 --- /dev/null +++ b/samples/CSharp/FormatSolution/Program.cs @@ -0,0 +1,60 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Build.Locator; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.MSBuild; + +// This program will format all Visual Basic and C# source files for an entire solution. +static class Program +{ + static async Task Main(string[] args) + { + // Locate and register the default instance of MSBuild installed on this machine. + MSBuildLocator.RegisterDefaults(); + + // The test solution is copied to the output directory when you build this sample. + MSBuildWorkspace workspace = MSBuildWorkspace.Create(); + + // Open the solution within the workspace. + Solution originalSolution = await workspace.OpenSolutionAsync(args[0]); + + // Declare a variable to store the intermediate solution snapshot at each step. + Solution newSolution = originalSolution; + + // Note how we can't simply iterate over originalSolution.Projects or project.Documents + // because it will return objects from the unmodified originalSolution, not from the newSolution. + // We need to use the ProjectIds and DocumentIds (that don't change) to look up the corresponding + // snapshots in the newSolution. + foreach (ProjectId projectId in originalSolution.ProjectIds) + { + // Look up the snapshot for the original project in the latest forked solution. + Project project = newSolution.GetProject(projectId); + + foreach (DocumentId documentId in project.DocumentIds) + { + // Look up the snapshot for the original document in the latest forked solution. + Document document = newSolution.GetDocument(documentId); + + // Get a transformed version of the document (a new solution snapshot is created + // under the covers to contain it - none of the existing objects are modified). + Document newDocument = await Formatter.FormatAsync(document); + + // Store the solution implicitly constructed in the previous step as the latest + // one so we can continue building it up in the next iteration. + newSolution = newDocument.Project.Solution; + } + } + + // Actually apply the accumulated changes and save them to disk. At this point + // workspace.CurrentSolution is updated to point to the new solution. + if (workspace.TryApplyChanges(newSolution)) + { + Console.WriteLine("Solution updated."); + } + else + { + Console.WriteLine("Update failed!"); + } + } +} diff --git a/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.Vsix/ImplementNotifyPropertyChanged.CSharp.Vsix.csproj b/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.Vsix/ImplementNotifyPropertyChanged.CSharp.Vsix.csproj new file mode 100644 index 000000000..be4fd7e66 --- /dev/null +++ b/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.Vsix/ImplementNotifyPropertyChanged.CSharp.Vsix.csproj @@ -0,0 +1,22 @@ + + + + net472 + ImplementNotifyPropertyChanged.CSharp.Vsix + ImplementNotifyPropertyChanged.CSharp.Vsix + false + false + false + false + false + + + + + Designer + + + + + + \ No newline at end of file diff --git a/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.Vsix/source.extension.vsixmanifest b/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.Vsix/source.extension.vsixmanifest new file mode 100644 index 000000000..ef7bb2698 --- /dev/null +++ b/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.Vsix/source.extension.vsixmanifest @@ -0,0 +1,21 @@ + + + + + Implement INotifyPropertyChanged for C# + This is a sample extension to implement INotifyPropertyChanged using the .NET Compiler Platform ("Roslyn"). + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/CodeGeneration.cs b/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/CodeGeneration.cs new file mode 100644 index 000000000..840f70636 --- /dev/null +++ b/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/CodeGeneration.cs @@ -0,0 +1,244 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; + +namespace ImplementNotifyPropertyChangedCS +{ + internal static class CodeGeneration + { + internal static CompilationUnitSyntax ImplementINotifyPropertyChanged(CompilationUnitSyntax root, SemanticModel model, IEnumerable properties, Workspace workspace) + { + TypeDeclarationSyntax typeDeclaration = properties.First().PropertyDeclaration.FirstAncestorOrSelf(); + Dictionary backingFieldLookup = Enumerable.ToDictionary(properties, info => info.PropertyDeclaration, info => info.BackingFieldName); + + root = root.ReplaceNodes(properties.Select(p => p.PropertyDeclaration as SyntaxNode).Concat(new[] { typeDeclaration }), + (original, updated) => original.IsKind(SyntaxKind.PropertyDeclaration) + ? ExpandProperty((PropertyDeclarationSyntax)original, backingFieldLookup[(PropertyDeclarationSyntax)original]) as SyntaxNode + : ExpandType((TypeDeclarationSyntax)original, (TypeDeclarationSyntax)updated, properties.Where(p => p.NeedsBackingField), model, workspace)); + + return root + .WithUsing("System.Collections.Generic") + .WithUsing("System.ComponentModel"); + } + + private static CompilationUnitSyntax WithUsing(this CompilationUnitSyntax root, string name) + { + if (!root.Usings.Any(u => u.Name.ToString() == name)) + { + root = root.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(name)).WithAdditionalAnnotations(Formatter.Annotation)); + } + + return root; + } + + private static TypeDeclarationSyntax ExpandType(TypeDeclarationSyntax original, TypeDeclarationSyntax updated, IEnumerable properties, SemanticModel model, Workspace workspace) + { + Debug.Assert(original != updated); + + return updated + .WithBackingFields(properties, workspace) + .WithBaseType(original, model) + .WithPropertyChangedEvent(original, model, workspace) + .WithSetPropertyMethod(original, model, workspace); + } + + private static TypeDeclarationSyntax WithBackingFields(this TypeDeclarationSyntax node, IEnumerable properties, Workspace workspace) + { + // generate backing field for auto-props + foreach (ExpandablePropertyInfo p in properties) + { + MemberDeclarationSyntax fieldDecl = GenerateBackingField(p, workspace); + + // put field just before property + PropertyDeclarationSyntax currentProp = node.DescendantNodes().OfType().First(d => d.Identifier.Text == p.PropertyDeclaration.Identifier.Text); + node = node.InsertNodesBefore(currentProp, new[] { fieldDecl }); + } + + return node; + } + + private static MemberDeclarationSyntax GenerateBackingField(ExpandablePropertyInfo property, Workspace workspace) + { + SyntaxGenerator g = SyntaxGenerator.GetGenerator(workspace, LanguageNames.CSharp); + SyntaxNode type = g.TypeExpression(property.Type); + + FieldDeclarationSyntax fieldDecl = (FieldDeclarationSyntax)ParseMember(string.Format("private _fieldType_ {0};", property.BackingFieldName)); + return fieldDecl.ReplaceNode(fieldDecl.Declaration.Type, type).WithAdditionalAnnotations(Formatter.Annotation); + } + + private static MemberDeclarationSyntax ParseMember(string member) + { + MemberDeclarationSyntax decl = ((ClassDeclarationSyntax)SyntaxFactory.ParseCompilationUnit("class x {\r\n" + member + "\r\n}").Members[0]).Members[0]; + return decl.WithAdditionalAnnotations(Formatter.Annotation); + } + + private static TypeDeclarationSyntax AddMembers(this TypeDeclarationSyntax node, params MemberDeclarationSyntax[] members) + { + return AddMembers(node, (IEnumerable)members); + } + + private static TypeDeclarationSyntax AddMembers(this TypeDeclarationSyntax node, IEnumerable members) + { + if (node is ClassDeclarationSyntax classDecl) + { + return classDecl.WithMembers(classDecl.Members.AddRange(members)); + } + + if (node is StructDeclarationSyntax structDecl) + { + return structDecl.WithMembers(structDecl.Members.AddRange(members)); + } + + throw new InvalidOperationException(); + } + + private static PropertyDeclarationSyntax ExpandProperty(PropertyDeclarationSyntax property, string backingFieldName) + { + if (!ExpansionChecker.TryGetAccessors(property, out AccessorDeclarationSyntax getter, out AccessorDeclarationSyntax setter)) + { + throw new ArgumentException(); + } + + if (getter.Body == null) + { + StatementSyntax returnFieldStatement = SyntaxFactory.ParseStatement(string.Format("return {0};", backingFieldName)); + getter = getter + .WithBody(SyntaxFactory.Block(SyntaxFactory.SingletonList(returnFieldStatement))); + } + + getter = getter + .WithSemicolonToken(default(SyntaxToken)); + + StatementSyntax setPropertyStatement = SyntaxFactory.ParseStatement(string.Format("SetProperty(ref {0}, value, \"{1}\");", backingFieldName, property.Identifier.ValueText)); + setter = setter + .WithBody(SyntaxFactory.Block(SyntaxFactory.SingletonList(setPropertyStatement))) + .WithSemicolonToken(default(SyntaxToken)); + + PropertyDeclarationSyntax newProperty = property + .WithAccessorList(SyntaxFactory.AccessorList(SyntaxFactory.List(new[] { getter, setter }))) + .WithAdditionalAnnotations(Formatter.Annotation); + + return newProperty; + } + + private const string InterfaceName = "System.ComponentModel.INotifyPropertyChanged"; + + private static TypeDeclarationSyntax WithBaseType(this TypeDeclarationSyntax node, TypeDeclarationSyntax original, SemanticModel semanticModel) + { + INamedTypeSymbol classSymbol = semanticModel.GetDeclaredSymbol(original); + INamedTypeSymbol interfaceSymbol = semanticModel.Compilation.GetTypeByMetadataName(InterfaceName); + + // Does this class already implement INotifyPropertyChanged? If not, add it to the base list. + if (!classSymbol.AllInterfaces.Contains(interfaceSymbol)) + { + TypeSyntax baseTypeName = SyntaxFactory.ParseTypeName(InterfaceName) + .WithAdditionalAnnotations(Simplifier.Annotation); + + node = node.IsKind(SyntaxKind.ClassDeclaration) + ? ((ClassDeclarationSyntax)node).AddBaseListTypes(SyntaxFactory.SimpleBaseType(baseTypeName)) as TypeDeclarationSyntax + : ((StructDeclarationSyntax)node).AddBaseListTypes(SyntaxFactory.SimpleBaseType(baseTypeName)); + + // Add a formatting annotation to the base list to ensure that it gets formatted properly. + node = node.ReplaceNode( + node.BaseList, + node.BaseList.WithAdditionalAnnotations(Formatter.Annotation)); + } + + return node; + } + + private static TypeDeclarationSyntax WithPropertyChangedEvent(this TypeDeclarationSyntax node, TypeDeclarationSyntax original, SemanticModel semanticModel, Workspace workspace) + { + INamedTypeSymbol classSymbol = semanticModel.GetDeclaredSymbol(original); + INamedTypeSymbol interfaceSymbol = semanticModel.Compilation.GetTypeByMetadataName(InterfaceName); + IEventSymbol propertyChangedEventSymbol = (IEventSymbol)interfaceSymbol.GetMembers("PropertyChanged").Single(); + ISymbol propertyChangedEvent = classSymbol.FindImplementationForInterfaceMember(propertyChangedEventSymbol); + + // Does this class contain an implementation for the PropertyChanged event? If not, add it. + if (propertyChangedEvent == null) + { + MemberDeclarationSyntax propertyChangedEventDecl = GeneratePropertyChangedEvent(); + node = node.AddMembers(propertyChangedEventDecl); + } + + return node; + } + + internal static MemberDeclarationSyntax GeneratePropertyChangedEvent() + { + EventFieldDeclarationSyntax decl = (EventFieldDeclarationSyntax)ParseMember("public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;"); + return decl.ReplaceNode(decl.Declaration.Type, decl.Declaration.Type.WithAdditionalAnnotations(Simplifier.Annotation)); + } + + private static IMethodSymbol FindSetPropertyMethod(this INamedTypeSymbol classSymbol, Compilation compilation) + { + // Find SetProperty(ref T, T, string) method. + IMethodSymbol setPropertyMethod = classSymbol + .GetMembers("SetProperty") + .OfType() + .FirstOrDefault(m => m.Parameters.Length == 3 && m.TypeParameters.Length == 1); + + if (setPropertyMethod != null) + { + System.Collections.Immutable.ImmutableArray parameters = setPropertyMethod.Parameters; + ITypeParameterSymbol typeParameter = setPropertyMethod.TypeParameters[0]; + INamedTypeSymbol stringType = compilation.GetSpecialType(SpecialType.System_String); + + if (setPropertyMethod.ReturnsVoid && + parameters[0].RefKind == RefKind.Ref && + parameters[0].Type.Equals(typeParameter) && + parameters[1].Type.Equals(typeParameter) && + parameters[2].Type.Equals(stringType)) + { + return setPropertyMethod; + } + } + + return null; + } + + private static TypeDeclarationSyntax WithSetPropertyMethod(this TypeDeclarationSyntax node, TypeDeclarationSyntax original, SemanticModel semanticModel, Workspace workspace) + { + INamedTypeSymbol classSymbol = semanticModel.GetDeclaredSymbol(original); + INamedTypeSymbol interfaceSymbol = semanticModel.Compilation.GetTypeByMetadataName(InterfaceName); + IEventSymbol propertyChangedEventSymbol = (IEventSymbol)interfaceSymbol.GetMembers("PropertyChanged").Single(); + ISymbol propertyChangedEvent = classSymbol.FindImplementationForInterfaceMember(propertyChangedEventSymbol); + + IMethodSymbol setPropertyMethod = classSymbol.FindSetPropertyMethod(semanticModel.Compilation); + if (setPropertyMethod == null) + { + MethodDeclarationSyntax setPropertyDecl = GenerateSetPropertyMethod(); + node = AddMembers(node, setPropertyDecl); + } + + return node; + } + + internal static MethodDeclarationSyntax GenerateSetPropertyMethod() + { + return (MethodDeclarationSyntax)ParseMember(@" +private void SetProperty(ref T field, T value, string name) +{ + if (!System.Collections.Generic.EqualityComparer.Default.Equals(field, value)) + { + field = value; + + var handler = PropertyChanged; + if (handler != null) + { + handler(this, new System.ComponentModel.PropertyChangedEventArgs(name)); + } + } +}").WithAdditionalAnnotations(Simplifier.Annotation); + } + } +} diff --git a/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ExpandablePropertyInfo.cs b/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ExpandablePropertyInfo.cs new file mode 100644 index 000000000..db8c2d1ad --- /dev/null +++ b/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ExpandablePropertyInfo.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ImplementNotifyPropertyChangedCS +{ + internal class ExpandablePropertyInfo + { + public string BackingFieldName { get; internal set; } + public bool NeedsBackingField { get; internal set; } + public PropertyDeclarationSyntax PropertyDeclaration { get; internal set; } + public ITypeSymbol Type { get; internal set; } + } +} diff --git a/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ExpansionChecker.cs b/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ExpansionChecker.cs new file mode 100644 index 000000000..cb1901aaa --- /dev/null +++ b/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ExpansionChecker.cs @@ -0,0 +1,325 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace ImplementNotifyPropertyChangedCS +{ + internal static class ExpansionChecker + { + internal static IEnumerable GetExpandableProperties(TextSpan span, SyntaxNode root, SemanticModel model) + { + IEnumerable> propertiesInTypes = root.DescendantNodes(span) + .OfType() + .Select(p => GetExpandablePropertyInfo(p, model)) + .Where(p => p != null) + .GroupBy(p => p.PropertyDeclaration.FirstAncestorOrSelf()); + + return propertiesInTypes.Any() + ? propertiesInTypes.First() + : Enumerable.Empty(); + } + + /// + /// Returns true if the specified can be expanded to include + /// support for . + /// + internal static ExpandablePropertyInfo GetExpandablePropertyInfo(PropertyDeclarationSyntax property, SemanticModel model) + { + // Don't expand properties with parse errors. + if (property.ContainsDiagnostics) + { + return null; + } + + if (property.Modifiers.Any(SyntaxKind.StaticKeyword) || property.Modifiers.Any(SyntaxKind.AbstractKeyword)) + { + return null; + } + + if (!TryGetAccessors(property, out AccessorDeclarationSyntax getter, out AccessorDeclarationSyntax setter)) + { + return null; + } + + if (getter.Body == null && setter.Body == null) + { + return new ExpandablePropertyInfo + { + PropertyDeclaration = property, + BackingFieldName = GenerateFieldName(property, model), + NeedsBackingField = true, + Type = model.GetDeclaredSymbol(property).Type + }; + } + + return (TryGetBackingFieldFromExpandableGetter(getter, model, out IFieldSymbol backingField) + && IsExpandableSetter(setter, model, backingField)) + ? new ExpandablePropertyInfo { PropertyDeclaration = property, BackingFieldName = backingField.Name } + : null; + } + + /// + /// Retrieves the get and set accessor declarations of the specified property. + /// Returns true if both get and set accessors exist; otherwise false. + /// + internal static bool TryGetAccessors( + PropertyDeclarationSyntax property, + out AccessorDeclarationSyntax getter, + out AccessorDeclarationSyntax setter) + { + SyntaxList accessors = property.AccessorList.Accessors; + getter = accessors.FirstOrDefault(ad => ad.Kind() == SyntaxKind.GetAccessorDeclaration); + setter = accessors.FirstOrDefault(ad => ad.Kind() == SyntaxKind.SetAccessorDeclaration); + + return accessors.Count == 2 && getter != null && setter != null; + } + + private static string GenerateFieldName(PropertyDeclarationSyntax property, SemanticModel semanticModel) + { + string baseName = property.Identifier.ValueText; + baseName = char.ToLower(baseName[0]).ToString() + baseName.Substring(1); + + IPropertySymbol propertySymbol = semanticModel.GetDeclaredSymbol(property); + if (propertySymbol == null || + propertySymbol.ContainingType == null) + { + return baseName; + } + + int index = 0; + string name = baseName; + while (propertySymbol.ContainingType.MemberNames.Contains(name)) + { + name = baseName + (++index).ToString(); + } + + return name; + } + + private static IFieldSymbol GetBackingFieldFromGetter( + AccessorDeclarationSyntax getter, + SemanticModel semanticModel) + { + // The getter should have a body containing a single return of a backing field. + + if (getter.Body == null) + { + return null; + } + + SyntaxList statements = getter.Body.Statements; + if (statements.Count != 1) + { + return null; + } + + if (!(statements.Single() is ReturnStatementSyntax returnStatement) || returnStatement.Expression == null) + { + return null; + } + + return semanticModel.GetSymbolInfo(returnStatement.Expression).Symbol as IFieldSymbol; + } + + private static bool TryGetBackingFieldFromExpandableGetter( + AccessorDeclarationSyntax getter, + SemanticModel semanticModel, + out IFieldSymbol backingField) + { + backingField = GetBackingFieldFromGetter(getter, semanticModel); + return backingField != null; + } + + private static bool IsBackingField(ExpressionSyntax expression, IFieldSymbol backingField, SemanticModel semanticModel) + { + return semanticModel + .GetSymbolInfo(expression).Symbol + .Equals(backingField); + } + + private static bool IsPropertyValueParameter(ExpressionSyntax expression, SemanticModel semanticModel) + { + + return semanticModel.GetSymbolInfo(expression).Symbol is IParameterSymbol parameterSymbol && + parameterSymbol.IsImplicitlyDeclared && + parameterSymbol.Name == "value"; + } + + private static bool IsAssignmentOfPropertyValueParameterToBackingField( + ExpressionSyntax expression, + IFieldSymbol backingField, + SemanticModel semanticModel) + { + if (expression.Kind() != SyntaxKind.SimpleAssignmentExpression) + { + return false; + } + + AssignmentExpressionSyntax assignment = (AssignmentExpressionSyntax)expression; + + return IsBackingField(assignment.Left, backingField, semanticModel) + && IsPropertyValueParameter(assignment.Right, semanticModel); + } + + private static bool ComparesPropertyValueParameterAndBackingField( + BinaryExpressionSyntax expression, + IFieldSymbol backingField, + SemanticModel semanticModel) + { + return (IsPropertyValueParameter(expression.Right, semanticModel) && IsBackingField(expression.Left, backingField, semanticModel)) + || (IsPropertyValueParameter(expression.Left, semanticModel) && IsBackingField(expression.Right, backingField, semanticModel)); + } + + private static bool IsExpandableSetter(AccessorDeclarationSyntax setter, SemanticModel semanticModel, IFieldSymbol backingField) + { + // The setter should have a body containing one of the following heuristic patterns or + // no body at all. + // + // Patterns: + // field = value; + // if (field != value) field = value; + // if (field == value) return; field = value; + + if (setter.Body == null) + { + return false; + } + + return IsExpandableSetterPattern1(setter, backingField, semanticModel) + || IsExpandableSetterPattern2(setter, backingField, semanticModel) + || IsExpandableSetterPattern3(setter, backingField, semanticModel); + } + + private static bool IsExpandableSetterPattern1( + AccessorDeclarationSyntax setter, + IFieldSymbol backingField, + SemanticModel semanticModel) + { + // Pattern: field = value + Debug.Assert(setter.Body != null); + + SyntaxList statements = setter.Body.Statements; + if (statements.Count != 1) + { + return false; + } + + return statements[0] is ExpressionStatementSyntax expressionStatement && + IsAssignmentOfPropertyValueParameterToBackingField(expressionStatement.Expression, backingField, semanticModel); + } + + private static bool IsExpandableSetterPattern2( + AccessorDeclarationSyntax setter, + IFieldSymbol backingField, + SemanticModel semanticModel) + { + // Pattern: if (field != value) field = value; + Debug.Assert(setter.Body != null); + + SyntaxList statements = setter.Body.Statements; + if (statements.Count != 1) + { + return false; + } + + if (!(statements[0] is IfStatementSyntax ifStatement)) + { + return false; + } + + StatementSyntax statement = ifStatement.Statement; + if (statement is BlockSyntax) + { + SyntaxList blockStatements = ((BlockSyntax)statement).Statements; + if (blockStatements.Count != 1) + { + return false; + } + + statement = blockStatements[0]; + } + + if (!(statement is ExpressionStatementSyntax expressionStatement)) + { + return false; + } + + if (!IsAssignmentOfPropertyValueParameterToBackingField(expressionStatement.Expression, backingField, semanticModel)) + { + return false; + } + + if (!(ifStatement.Condition is BinaryExpressionSyntax condition) || + condition.Kind() != SyntaxKind.NotEqualsExpression) + { + return false; + } + + return ComparesPropertyValueParameterAndBackingField(condition, backingField, semanticModel); + } + + private static bool IsExpandableSetterPattern3( + AccessorDeclarationSyntax setter, + IFieldSymbol backingField, + SemanticModel semanticModel) + { + // Pattern: if (field == value) return; field = value; + + Debug.Assert(setter.Body != null); + + SyntaxList statements = setter.Body.Statements; + if (statements.Count != 2) + { + return false; + } + + if (!(statements[0] is IfStatementSyntax ifStatement)) + { + return false; + } + + StatementSyntax statement = ifStatement.Statement; + if (statement is BlockSyntax) + { + SyntaxList blockStatements = ((BlockSyntax)statement).Statements; + if (blockStatements.Count != 1) + { + return false; + } + + statement = blockStatements[0]; + } + + if (!(statement is ReturnStatementSyntax returnStatement) || + returnStatement.Expression != null) + { + return false; + } + + if (!(statements[1] is ExpressionStatementSyntax expressionStatement)) + { + return false; + } + + if (!IsAssignmentOfPropertyValueParameterToBackingField(expressionStatement.Expression, backingField, semanticModel)) + { + return false; + } + + if (!(ifStatement.Condition is BinaryExpressionSyntax condition) || + condition.Kind() != SyntaxKind.EqualsExpression) + { + return false; + } + + return ComparesPropertyValueParameterAndBackingField(condition, backingField, semanticModel); + } + } +} diff --git a/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.CSharp.csproj b/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.CSharp.csproj new file mode 100644 index 000000000..a921d2631 --- /dev/null +++ b/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.CSharp.csproj @@ -0,0 +1,12 @@ + + + + netstandard1.3 + + + + + + + + diff --git a/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChangedCodeRefactoringProvider.cs b/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChangedCodeRefactoringProvider.cs new file mode 100644 index 000000000..61be3963b --- /dev/null +++ b/samples/CSharp/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChangedCodeRefactoringProvider.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; + +namespace ImplementNotifyPropertyChangedCS +{ + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = "ImplementNotifyPropertyChangedCS"), Shared] + internal partial class ImplementNotifyPropertyChangedCodeRefactoringProvider : CodeRefactoringProvider + { + public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + Document document = context.Document; + Microsoft.CodeAnalysis.Text.TextSpan textSpan = context.Span; + CancellationToken cancellationToken = context.CancellationToken; + + CompilationUnitSyntax root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false) as CompilationUnitSyntax; + SemanticModel model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + // if length is 0 then no particular range is selected, so pick the first enclosing member + if (textSpan.Length == 0) + { + MemberDeclarationSyntax decl = root.FindToken(textSpan.Start).Parent.AncestorsAndSelf().OfType().FirstOrDefault(); + if (decl != null) + { + textSpan = decl.FullSpan; + } + } + + IEnumerable properties = ExpansionChecker.GetExpandableProperties(textSpan, root, model); + + if (properties.Any()) + { + CodeAction action = CodeAction.Create( + "Apply INotifyPropertyChanged pattern", + c => ImplementNotifyPropertyChangedAsync(document, root, model, properties, c), + equivalenceKey: nameof(ImplementNotifyPropertyChangedCodeRefactoringProvider)); + + context.RegisterRefactoring(action); + } + } + + private async Task ImplementNotifyPropertyChangedAsync(Document document, CompilationUnitSyntax root, SemanticModel model, IEnumerable properties, CancellationToken cancellationToken) + { + document = document.WithSyntaxRoot(CodeGeneration.ImplementINotifyPropertyChanged(root, model, properties, document.Project.Solution.Workspace)); + document = await Simplifier.ReduceAsync(document, Simplifier.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); + document = await Formatter.FormatAsync(document, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); + return document; + } + } +} diff --git a/samples/CSharp/MakeConst/MakeConst.Implementation/MakeConst.CSharp.csproj b/samples/CSharp/MakeConst/MakeConst.Implementation/MakeConst.CSharp.csproj new file mode 100644 index 000000000..cb7a25b15 --- /dev/null +++ b/samples/CSharp/MakeConst/MakeConst.Implementation/MakeConst.CSharp.csproj @@ -0,0 +1,32 @@ + + + + netstandard1.3 + false + True + + + + MakeConst + 0.0.1 + Microsoft + https://github.com/dotnet/roslyn-sdk + false + MakeConst + Detect when variable can be made constant. + Copyright + MakeConst, analyzers + true + + + + + + + + + + + + + diff --git a/samples/CSharp/MakeConst/MakeConst.Implementation/MakeConstAnalyzer.cs b/samples/CSharp/MakeConst/MakeConst.Implementation/MakeConstAnalyzer.cs new file mode 100644 index 000000000..329018a9a --- /dev/null +++ b/samples/CSharp/MakeConst/MakeConst.Implementation/MakeConstAnalyzer.cs @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace MakeConst +{ + // Implementing syntax node analyzer because the make const diagnostics in one method body are not dependent on the contents of other method bodies. + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class MakeConstAnalyzer : DiagnosticAnalyzer + { + public const string MakeConstDiagnosticId = "MakeConst"; + + public static readonly DiagnosticDescriptor MakeConstRule = + new DiagnosticDescriptor(MakeConstDiagnosticId, + "Make Constant", + "Can be made const", + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(MakeConstRule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement); + } + + private void AnalyzeNode(SyntaxNodeAnalysisContext context) + { + if (CanBeMadeConst((LocalDeclarationStatementSyntax)context.Node, context.SemanticModel)) + { + context.ReportDiagnostic(Diagnostic.Create(MakeConstRule, context.Node.GetLocation())); + } + } + + private bool CanBeMadeConst(LocalDeclarationStatementSyntax localDeclaration, SemanticModel semanticModel) + { + // already const? + if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword)) + { + return false; + } + + // Ensure that all variables in the local declaration have initializers that + // are assigned with constant values. + foreach (VariableDeclaratorSyntax variable in localDeclaration.Declaration.Variables) + { + EqualsValueClauseSyntax initializer = variable.Initializer; + if (initializer == null) + { + return false; + } + + Optional constantValue = semanticModel.GetConstantValue(initializer.Value); + if (!constantValue.HasValue) + { + return false; + } + + TypeSyntax variableTypeName = localDeclaration.Declaration.Type; + ITypeSymbol variableType = semanticModel.GetTypeInfo(variableTypeName).ConvertedType; + + // Ensure that the initializer value can be converted to the type of the + // local declaration without a user-defined conversion. + Conversion conversion = semanticModel.ClassifyConversion(initializer.Value, variableType); + if (!conversion.Exists || conversion.IsUserDefined) + { + return false; + } + + // Special cases: + // * If the constant value is a string, the type of the local declaration + // must be System.String. + // * If the constant value is null, the type of the local declaration must + // be a reference type. + if (constantValue.Value is string) + { + if (variableType.SpecialType != SpecialType.System_String) + { + return false; + } + } + else if (variableType.IsReferenceType && constantValue.Value != null) + { + return false; + } + } + + // Perform data flow analysis on the local declaration. + DataFlowAnalysis dataFlowAnalysis = semanticModel.AnalyzeDataFlow(localDeclaration); + + // Retrieve the local symbol for each variable in the local declaration + // and ensure that it is not written outside of the data flow analysis region. + foreach (VariableDeclaratorSyntax variable in localDeclaration.Declaration.Variables) + { + ISymbol variableSymbol = semanticModel.GetDeclaredSymbol(variable); + if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol)) + { + return false; + } + } + + return true; + } + } +} diff --git a/samples/CSharp/MakeConst/MakeConst.Implementation/MakeConstCodeFixProvider.cs b/samples/CSharp/MakeConst/MakeConst.Implementation/MakeConstCodeFixProvider.cs new file mode 100644 index 000000000..87bb23a6f --- /dev/null +++ b/samples/CSharp/MakeConst/MakeConst.Implementation/MakeConstCodeFixProvider.cs @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; + +namespace MakeConst +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(MakeConstCodeFixProvider)), Shared] + public sealed class MakeConstCodeFixProvider : CodeFixProvider + { + public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(MakeConstAnalyzer.MakeConstDiagnosticId); + + public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + SyntaxNode root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + + Diagnostic diagnostic = context.Diagnostics.First(); + Microsoft.CodeAnalysis.Text.TextSpan diagnosticSpan = diagnostic.Location.SourceSpan; + + // Find the local declaration identified by the diagnostic. + LocalDeclarationStatementSyntax declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); + + // Register a code action that will invoke the fix. + CodeAction action = CodeAction.Create( + "Make constant", + c => MakeConstAsync(context.Document, declaration, c), + equivalenceKey: nameof(MakeConstCodeFixProvider)); + + context.RegisterCodeFix(action, diagnostic); + } + + private static async Task MakeConstAsync(Document document, LocalDeclarationStatementSyntax localDeclaration, CancellationToken cancellationToken) + { + // Remove the leading trivia from the local declaration. + SyntaxToken firstToken = localDeclaration.GetFirstToken(); + SyntaxTriviaList leadingTrivia = firstToken.LeadingTrivia; + LocalDeclarationStatementSyntax trimmedLocal = localDeclaration.ReplaceToken( + firstToken, firstToken.WithLeadingTrivia(SyntaxTriviaList.Empty)); + + // Create a const token with the leading trivia. + SyntaxToken constToken = SyntaxFactory.Token(leadingTrivia, SyntaxKind.ConstKeyword, SyntaxFactory.TriviaList(SyntaxFactory.ElasticMarker)); + + // Insert the const token into the modifiers list, creating a new modifiers list. + SyntaxTokenList newModifiers = trimmedLocal.Modifiers.Insert(0, constToken); + + // If the type of declaration is 'var', create a new type name for the + // type inferred for 'var'. + VariableDeclarationSyntax variableDeclaration = localDeclaration.Declaration; + TypeSyntax variableTypeName = variableDeclaration.Type; + if (variableTypeName.IsVar) + { + SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + // Special case: Ensure that 'var' isn't actually an alias to another type + // (e.g. using var = System.String). + IAliasSymbol aliasInfo = semanticModel.GetAliasInfo(variableTypeName, cancellationToken); + if (aliasInfo == null) + { + // Retrieve the type inferred for var. + ITypeSymbol type = semanticModel.GetTypeInfo(variableTypeName, cancellationToken).ConvertedType; + + // Special case: Ensure that 'var' isn't actually a type named 'var'. + if (type.Name != "var") + { + // Create a new TypeSyntax for the inferred type. Be careful + // to keep any leading and trailing trivia from the var keyword. + TypeSyntax typeName = SyntaxFactory.ParseTypeName(type.ToDisplayString()) + .WithLeadingTrivia(variableTypeName.GetLeadingTrivia()) + .WithTrailingTrivia(variableTypeName.GetTrailingTrivia()); + + // Add an annotation to simplify the type name. + TypeSyntax simplifiedTypeName = typeName.WithAdditionalAnnotations(Simplifier.Annotation); + + // Replace the type in the variable declaration. + variableDeclaration = variableDeclaration.WithType(simplifiedTypeName); + } + } + } + + // Produce the new local declaration. + LocalDeclarationStatementSyntax newLocal = trimmedLocal.WithModifiers(newModifiers) + .WithDeclaration(variableDeclaration); + + // Add an annotation to format the new local declaration. + LocalDeclarationStatementSyntax formattedLocal = newLocal.WithAdditionalAnnotations(Formatter.Annotation); + + // Replace the old local declaration with the new local declaration. + SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + SyntaxNode newRoot = root.ReplaceNode(localDeclaration, formattedLocal); + + // Return document with transformed tree. + return document.WithSyntaxRoot(newRoot); + } + } +} diff --git a/samples/CSharp/MakeConst/MakeConst.Implementation/tools/install.ps1 b/samples/CSharp/MakeConst/MakeConst.Implementation/tools/install.ps1 new file mode 100644 index 000000000..c1c3d8822 --- /dev/null +++ b/samples/CSharp/MakeConst/MakeConst.Implementation/tools/install.ps1 @@ -0,0 +1,58 @@ +param($installPath, $toolsPath, $package, $project) + +if($project.Object.SupportsPackageDependencyResolution) +{ + if($project.Object.SupportsPackageDependencyResolution()) + { + # Do not install analyzers via install.ps1, instead let the project system handle it. + return + } +} + +$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve + +foreach($analyzersPath in $analyzersPaths) +{ + if (Test-Path $analyzersPath) + { + # Install the language agnostic analyzers. + foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) + } + } + } +} + +# $project.Type gives the language name like (C# or VB.NET) +$languageFolder = "" +if($project.Type -eq "C#") +{ + $languageFolder = "cs" +} +if($project.Type -eq "VB.NET") +{ + $languageFolder = "vb" +} +if($languageFolder -eq "") +{ + return +} + +foreach($analyzersPath in $analyzersPaths) +{ + # Install language specific analyzers. + $languageAnalyzersPath = join-path $analyzersPath $languageFolder + if (Test-Path $languageAnalyzersPath) + { + foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) + } + } + } +} \ No newline at end of file diff --git a/samples/CSharp/MakeConst/MakeConst.Implementation/tools/uninstall.ps1 b/samples/CSharp/MakeConst/MakeConst.Implementation/tools/uninstall.ps1 new file mode 100644 index 000000000..65a862370 --- /dev/null +++ b/samples/CSharp/MakeConst/MakeConst.Implementation/tools/uninstall.ps1 @@ -0,0 +1,65 @@ +param($installPath, $toolsPath, $package, $project) + +if($project.Object.SupportsPackageDependencyResolution) +{ + if($project.Object.SupportsPackageDependencyResolution()) + { + # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it. + return + } +} + +$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve + +foreach($analyzersPath in $analyzersPaths) +{ + # Uninstall the language agnostic analyzers. + if (Test-Path $analyzersPath) + { + foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) + } + } + } +} + +# $project.Type gives the language name like (C# or VB.NET) +$languageFolder = "" +if($project.Type -eq "C#") +{ + $languageFolder = "cs" +} +if($project.Type -eq "VB.NET") +{ + $languageFolder = "vb" +} +if($languageFolder -eq "") +{ + return +} + +foreach($analyzersPath in $analyzersPaths) +{ + # Uninstall language specific analyzers. + $languageAnalyzersPath = join-path $analyzersPath $languageFolder + if (Test-Path $languageAnalyzersPath) + { + foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + try + { + $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) + } + catch + { + + } + } + } + } +} \ No newline at end of file diff --git a/samples/CSharp/MakeConst/MakeConst.Vsix/MakeConst.CSharp.Vsix.csproj b/samples/CSharp/MakeConst/MakeConst.Vsix/MakeConst.CSharp.Vsix.csproj new file mode 100644 index 000000000..2aadd8e3e --- /dev/null +++ b/samples/CSharp/MakeConst/MakeConst.Vsix/MakeConst.CSharp.Vsix.csproj @@ -0,0 +1,22 @@ + + + + net472 + MakeConst.CSharp.Vsix + MakeConst.CSharp.Vsix + false + false + false + false + false + + + + + Designer + + + + + + \ No newline at end of file diff --git a/samples/CSharp/MakeConst/MakeConst.Vsix/source.extension.vsixmanifest b/samples/CSharp/MakeConst/MakeConst.Vsix/source.extension.vsixmanifest new file mode 100644 index 000000000..46e05ab91 --- /dev/null +++ b/samples/CSharp/MakeConst/MakeConst.Vsix/source.extension.vsixmanifest @@ -0,0 +1,22 @@ + + + + + MakeConst + This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn"). + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/AddOutOrRefCodeAction.cs b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/AddOutOrRefCodeAction.cs new file mode 100644 index 000000000..5e7a023bd --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/AddOutOrRefCodeAction.cs @@ -0,0 +1,146 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using RefOutModifier.Properties; + +namespace Roslyn.Samples.AddOrRemoveRefOutModifier +{ + internal class AddOutOrRefCodeAction : CodeAction + { + private readonly Document document; + private readonly SemanticModel semanticModel; + private readonly ArgumentSyntax argument; + private readonly IEnumerable parameters; + + public static bool Applicable(SemanticModel semanticModel, ArgumentSyntax argument, IEnumerable parameters) + { + SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(argument.Expression); + ISymbol symbol = symbolInfo.Symbol; + + if (symbol == null) + { + return true; + } + + if (symbol.Kind != SymbolKind.Field && + symbol.Kind != SymbolKind.Parameter && + symbol.Kind != SymbolKind.Local) + { + return false; + } + + if (symbol is IFieldSymbol field) + { + return !field.IsReadOnly; + } + + return true; + } + + public AddOutOrRefCodeAction( + Document document, + SemanticModel semanticModel, + ArgumentSyntax argument, + IEnumerable parameters) + { + this.document = document; + this.semanticModel = semanticModel; + this.argument = argument; + this.parameters = parameters; + } + + private SyntaxToken GetOutOrRefModifier() + { + // special case where argument == parameter + SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(argument.Expression); + if (symbolInfo.Symbol != null && symbolInfo.Symbol.Kind == SymbolKind.Parameter) + { + if (IsSameParameter(symbolInfo.Symbol as IParameterSymbol, parameters)) + { + return SyntaxFactory.Token(SyntaxKind.OutKeyword); + } + } + + BaseMethodDeclarationSyntax method = parameters.Select(p => p.AncestorAndSelf()).FirstOrDefault(m => m.Body != null); + if (method == null) + { + return SyntaxFactory.Token(SyntaxKind.RefKeyword); + } + + DataFlowAnalysis dataFlow = semanticModel.AnalyzeDataFlow(method.Body); + if (ContainSameParameter(dataFlow.ReadInside, parameters)) + { + return SyntaxFactory.Token(SyntaxKind.RefKeyword); + } + + return ContainSameParameter(dataFlow.AlwaysAssigned, parameters) ? SyntaxFactory.Token(SyntaxKind.OutKeyword) : SyntaxFactory.Token(SyntaxKind.RefKeyword); + } + + private static bool ContainSameParameter(IEnumerable symbols, IEnumerable parameters) + { + foreach (ISymbol symbol in symbols) + { + if (!(symbol is IParameterSymbol parameterSymbol)) + { + continue; + } + + if (IsSameParameter(parameterSymbol, parameters)) + { + return true; + } + } + + return false; + } + + private static bool IsSameParameter(IParameterSymbol parameterSymbol, IEnumerable parameters) + { + IEnumerable parametersFromSymbol = parameterSymbol.Locations.Select(l => l.FindToken().AncestorAndSelf()); + if (parameters.Any(p => parametersFromSymbol.Any(p2 => p == p2))) + { + return true; + } + + return false; + } + + protected override async Task GetChangedDocumentAsync(CancellationToken cancellationToken) + { + SyntaxToken modifier = GetOutOrRefModifier(); + + Dictionary map = new Dictionary + { + { + argument, + SyntaxFactory.Argument(argument.NameColon, modifier, argument.Expression) + .WithAdditionalAnnotations(Formatter.Annotation) + } + }; + + foreach (ParameterSyntax parameter in parameters) + { + map.Add(parameter, + SyntaxFactory.Parameter(parameter.AttributeLists, parameter.Modifiers.Add(modifier), parameter.Type, parameter.Identifier, parameter.Default) + .WithAdditionalAnnotations(Formatter.Annotation)); + } + + SyntaxNode root = (SyntaxNode)await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + SyntaxNode newRoot = root.ReplaceNodes(map.Keys, (o, n) => map[o]); + + return document.WithSyntaxRoot(newRoot); + } + + public override string Title + { + get { return Resources.AddOutOrRefTitle; } + } + + } +} diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/ApplicableActionFinder.cs b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/ApplicableActionFinder.cs new file mode 100644 index 000000000..a6935cbd9 --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/ApplicableActionFinder.cs @@ -0,0 +1,266 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Text; + +namespace Roslyn.Samples.AddOrRemoveRefOutModifier +{ + internal class ApplicableActionFinder + { + private Document document; + private readonly int position; + private readonly CancellationToken cancellationToken; + + public ApplicableActionFinder(Document document, int position, CancellationToken cancellationToken) + { + this.document = document; + this.position = position; + this.cancellationToken = cancellationToken; + } + + public async Task<(TextSpan, CodeAction)> GetSpanAndActionAsync() + { + SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + SyntaxTree tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + if (!tree.OnArgumentOrParameter(position)) + { + return (default, null); + } + + SyntaxNode root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false); + SyntaxToken token = root.FindToken(position); + CodeAction action = await GetActionAsync(semanticModel, tree, token).ConfigureAwait(false); + if (action == null) + { + return (default, null); + } + + return (token.Span, action); + } + + private async Task GetActionAsync(SemanticModel semanticModel, SyntaxTree tree, SyntaxToken token) + { + IMethodSymbol methodSymbol = GetMethodDefinitionSymbol(semanticModel, token); + if (methodSymbol == null || methodSymbol.Locations.Any(l => l.IsInMetadata)) + { + // can't find method definition defined in source + return null; + } + + // adding or deleting ref/out on anonymous method/lambda not supported + if (methodSymbol.MethodKind == MethodKind.AnonymousFunction) + { + return null; + } + + (ArgumentSyntax argument, IEnumerable parameters) = await GetArgumentAndParametersAsync(semanticModel, methodSymbol, token).ConfigureAwait(false); + if (argument == null || parameters == null) + { + return null; + } + + // currently only support everything in one file + IEnumerable nodes = (new SyntaxNode[] { argument }).Concat(parameters); + if (document.Project.GetContainingDocuments(nodes, cancellationToken).Count() != 1) + { + return null; + } + + if (tree.OnArgumentOrParameterWithoutRefOut(position)) + { + return AddOutOrRefCodeAction.Applicable(semanticModel, argument, parameters) + ? new AddOutOrRefCodeAction(document, semanticModel, argument, parameters) + : null; + } + else + { + return RemoveOutOrRefCodeAction.Applicable(semanticModel, argument, parameters) + ? new RemoveOutOrRefCodeAction(document, semanticModel, argument, parameters) + : null; + } + } + + private async Task<(ArgumentSyntax, IEnumerable)> GetArgumentAndParametersAsync( + SemanticModel semanticModel, + IMethodSymbol methodSymbol, + SyntaxToken token) + { + (int parameterIndex, IEnumerable parameters) = GetParameterInfo(semanticModel, methodSymbol, token); + if (parameters == null) + { + return (null, null); + } + + ArgumentSyntax argument = await GetArgumentAsync(methodSymbol, parameterIndex, parameters.First()).ConfigureAwait(false); + if (argument == null) + { + return (null, null); + } + + return (argument, parameters); + } + + private async Task GetArgumentAsync(IMethodSymbol methodSymbol, int parameterIndex, ParameterSyntax parameter) + { + Solution solution = document.Project.Solution; + IEnumerable result = await SymbolFinder.FindReferencesAsync(methodSymbol, solution, cancellationToken: cancellationToken).ConfigureAwait(false); + if (result == null) + { + return null; + } + + // sample only supports having only one reference and from same project + if (result.Count() != 1 || + result.Single().Locations.Any(l => !l.Location.IsInSource)) + { + return null; + } + + InvocationExpressionSyntax invocation = result.Single() + .Locations + .Cast() + .Select(l => l.FindToken().AncestorAndSelf()) + .Single(); + + string parameterName = parameter.Identifier.ValueText; + List list = new List(); + + for (int i = 0; i < invocation.ArgumentList.Arguments.Count; i++) + { + // position based + ArgumentSyntax argument = invocation.ArgumentList.Arguments[i]; + if (argument.NameColon == null && i == parameterIndex) + { + return argument; + } + + // named parameter + if (argument.NameColon != null) + { + ArgumentSyntax namedArgument = invocation.ArgumentList + .Arguments + .Where(a => a.NameColon != null) + .FirstOrDefault(a => a.NameColon.Name.Identifier.ValueText == parameterName); + if (namedArgument == null) + { + return null; + } + + return namedArgument; + } + } + + return null; + } + + private (int, IEnumerable) GetParameterInfo( + SemanticModel semanticModel, + IMethodSymbol methodSymbol, + SyntaxToken token) + { + int parameterIndex = GetParameterIndex(methodSymbol, token); + if (parameterIndex < 0) + { + return (default, null); + } + + // find all parameter syntax for the index + IEnumerable parameters = methodSymbol.Locations + .Select(l => l.FindToken().AncestorAndSelf()) + .Select(n => n.ParameterList.Parameters[parameterIndex]); + + if (parameters.Count() > 1) + { + parameters = parameters.Reverse(); + } + + return (parameterIndex, parameters); + } + + private int GetParameterIndex(IMethodSymbol methodSymbol, SyntaxToken token) + { + ArgumentSyntax argument = token.AncestorAndSelf(); + if (argument != null) + { + // name parameter? + if (argument.NameColon != null) + { + IParameterSymbol symbol = methodSymbol.Parameters.FirstOrDefault(p => p.Name == argument.NameColon.Name.Identifier.ValueText); + if (symbol == null) + { + // named parameter is used but can't find one? + return -1; + } + + return symbol.Ordinal; + } + + // positional argument + ArgumentListSyntax list = argument.Parent as ArgumentListSyntax; + for (int i = 0; i < list.Arguments.Count; i++) + { + ArgumentSyntax arg = list.Arguments[i]; + + // malformed call + if (arg.NameColon != null) + { + return -1; + } + + if (arg == argument) + { + return i; + } + } + + return -1; + } + + ParameterSyntax parameter = token.AncestorAndSelf(); + if (parameter != null) + { + ParameterListSyntax parameterList = parameter.AncestorAndSelf(); + return parameterList.Parameters.IndexOf(parameter); + } + + return -1; + } + + private IMethodSymbol GetMethodDefinitionSymbol(SemanticModel semanticModel, SyntaxToken token) + { + ArgumentSyntax argument = token.AncestorAndSelf(); + if (argument != null) + { + InvocationExpressionSyntax invocation = argument.AncestorAndSelf(); + if (invocation == null) + { + return null; + } + + return semanticModel.GetSymbolInfo(invocation, cancellationToken).Symbol as IMethodSymbol; + } + + ParameterSyntax parameter = token.AncestorAndSelf(); + if (parameter != null) + { + ParameterListSyntax parameterList = parameter.AncestorAndSelf(); + if (parameterList == null) + { + // doesn't support lambda + return null; + } + + SyntaxNode definitionNode = parameterList.Parent; + return semanticModel.GetDeclaredSymbol(definitionNode, cancellationToken) as IMethodSymbol; + } + + return null; + } + } +} diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Extensions.cs b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Extensions.cs new file mode 100644 index 000000000..1496a7886 --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Extensions.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Roslyn.Samples.AddOrRemoveRefOutModifier +{ + // Miscellaneous helper extension methods over many different classes + internal static class Extensions + { + public static bool IsEmpty(this IEnumerable list) + { + // if the thing in the list is null, it still means empty + T first = list.FirstOrDefault(); + return first == null; + } + + public static async Task GetCSharpSyntaxTreeAsync(this Document document, CancellationToken token) + { + return (SyntaxTree)await document.GetSyntaxTreeAsync(token).ConfigureAwait(false); + } + + public static bool OnArgumentOrParameter(this SyntaxTree tree, int position) + { + SyntaxToken token = tree.GetRoot().FindToken(position); + if (token.Kind() == SyntaxKind.None) + { + return false; + } + + ArgumentSyntax argument = token.AncestorAndSelf(); + if (argument != null && argument.Span.IntersectsWith(position)) + { + return true; + } + + ParameterSyntax parameter = token.AncestorAndSelf(); + if (parameter != null && parameter.Span.IntersectsWith(position)) + { + return true; + } + + return false; + } + + public static bool OnArgumentOrParameterWithoutRefOut(this SyntaxTree tree, int position) + { + SyntaxToken token = tree.GetRoot().FindToken(position); + if (token.Kind() == SyntaxKind.None) + { + return false; + } + + ArgumentSyntax argument = token.AncestorAndSelf(); + if (argument != null && argument.Span.IntersectsWith(position)) + { + if (argument.RefOrOutKeyword.Kind() != SyntaxKind.None) + { + return false; + } + + return true; + } + + ParameterSyntax parameter = token.AncestorAndSelf(); + if (parameter != null && parameter.Span.IntersectsWith(position)) + { + if (parameter.Modifiers.Any(m => m.Kind() == SyntaxKind.OutKeyword || m.Kind() == SyntaxKind.RefKeyword)) + { + return false; + } + + return true; + } + + return false; + } + + public static bool OnSpecificToken(this SyntaxTree tree, SyntaxKind tokenKind, int position) + { + SyntaxToken token = tree.GetRoot().FindToken(position); + if (!token.IsKind(tokenKind)) + { + return false; + } + + if (!token.Span.IntersectsWith(position)) + { + return false; + } + + // token must belong to either argument or parameter + if (!token.Parent.IsKind(SyntaxKind.Argument) && + !token.Parent.IsKind(SyntaxKind.Parameter)) + { + return false; + } + + return true; + } + + public static T AncestorAndSelf(this SyntaxToken token) where T : SyntaxNode + { + return token.Parent.AncestorAndSelf(); + } + + public static T AncestorAndSelf(this SyntaxNode node) where T : SyntaxNode + { + return node.AncestorsAndSelf().FirstOrDefault(n => n is T) as T; + } + + public static SyntaxToken FindToken(this Location location) + { + return ((SyntaxTree)location.SourceTree).GetRoot().FindToken(location.SourceSpan.Start); + } + + public static IEnumerable GetContainingDocuments(this Project project, IEnumerable nodes, CancellationToken cancellationToken) + { + return nodes.Where(n => n.SyntaxTree != null).Select(n => project.GetDocument(n.SyntaxTree)).Distinct(); + } + + public static SyntaxTokenList Add(this SyntaxTokenList list, SyntaxToken token) + { + List tokens = new List(list) + { + token + }; + + return SyntaxFactory.TokenList(tokens); + } + + public static SyntaxToken MergeTrailingTrivia(this SyntaxToken token, SyntaxToken tokenToRemove) + { + // this has a bug where if tokenToRemove is the first token on line, trivia should be + // attached to leading trivia of next token, not trailing trivia of previous token + List trivia = new List(token.TrailingTrivia); + trivia.AddRange(tokenToRemove.LeadingTrivia); + trivia.AddRange(tokenToRemove.TrailingTrivia); + + return token.WithTrailingTrivia(SyntaxFactory.TriviaList(trivia)); + } + } +} diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/Resources.Designer.cs b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/Resources.Designer.cs new file mode 100644 index 000000000..fcf706891 --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/Resources.Designer.cs @@ -0,0 +1,82 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace RefOutModifier.Properties { + using System; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RefOutModifier.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Add Out/Ref Modifier. + /// + internal static string AddOutOrRefTitle { + get { + return ResourceManager.GetString("AddOutOrRefTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Remove Out/Ref Modifier. + /// + internal static string RemoveOutOrRefTitle { + get { + return ResourceManager.GetString("RemoveOutOrRefTitle", resourceCulture); + } + } + } +} diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/Resources.resx b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/Resources.resx new file mode 100644 index 000000000..6747b26fe --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Add Out/Ref Modifier + + + Remove Out/Ref Modifier + + \ No newline at end of file diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.cs.xlf b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.cs.xlf new file mode 100644 index 000000000..58d6b9653 --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.cs.xlf @@ -0,0 +1,17 @@ + + + + + + Add Out/Ref Modifier + Přidat modifikátor Out/Ref + + + + Remove Out/Ref Modifier + Odebrat modifikátor Out/Ref + + + + + \ No newline at end of file diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.de.xlf b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.de.xlf new file mode 100644 index 000000000..dcdecd10c --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.de.xlf @@ -0,0 +1,17 @@ + + + + + + Add Out/Ref Modifier + Out/Ref-Modifizierer hinzufügen + + + + Remove Out/Ref Modifier + Out/Ref-Modifizierer entfernen + + + + + \ No newline at end of file diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.es.xlf b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.es.xlf new file mode 100644 index 000000000..bdfbe407e --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.es.xlf @@ -0,0 +1,17 @@ + + + + + + Add Out/Ref Modifier + Agregar modificador Out/Ref + + + + Remove Out/Ref Modifier + Eliminar modificador Out/Ref + + + + + \ No newline at end of file diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.fr.xlf b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.fr.xlf new file mode 100644 index 000000000..42992ca42 --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.fr.xlf @@ -0,0 +1,17 @@ + + + + + + Add Out/Ref Modifier + Ajouter le modificateur Out/Ref + + + + Remove Out/Ref Modifier + Supprimer le modificateur Out/Ref + + + + + \ No newline at end of file diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.it.xlf b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.it.xlf new file mode 100644 index 000000000..9b5d92268 --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.it.xlf @@ -0,0 +1,17 @@ + + + + + + Add Out/Ref Modifier + Aggiungi modificatore Out/Ref + + + + Remove Out/Ref Modifier + Rimuovi modificatore Out/Ref + + + + + \ No newline at end of file diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.ja.xlf b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.ja.xlf new file mode 100644 index 000000000..11ac74a98 --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.ja.xlf @@ -0,0 +1,17 @@ + + + + + + Add Out/Ref Modifier + Out/Ref 修飾子の追加 + + + + Remove Out/Ref Modifier + Out/Ref 修飾子の削除 + + + + + \ No newline at end of file diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.ko.xlf b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.ko.xlf new file mode 100644 index 000000000..89de4c407 --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.ko.xlf @@ -0,0 +1,17 @@ + + + + + + Add Out/Ref Modifier + Out/Ref 한정자 추가 + + + + Remove Out/Ref Modifier + Out/Ref 한정자 + + + + + \ No newline at end of file diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.pl.xlf b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.pl.xlf new file mode 100644 index 000000000..0858c0ac7 --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.pl.xlf @@ -0,0 +1,17 @@ + + + + + + Add Out/Ref Modifier + Dodaj modyfikator Out/Ref + + + + Remove Out/Ref Modifier + Usuń modyfikator Out/Ref + + + + + \ No newline at end of file diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.pt-BR.xlf b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.pt-BR.xlf new file mode 100644 index 000000000..ae4464946 --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.pt-BR.xlf @@ -0,0 +1,17 @@ + + + + + + Add Out/Ref Modifier + Adicionar Modificador Sai/Ref + + + + Remove Out/Ref Modifier + Remover Modificador Sai/Ref + + + + + \ No newline at end of file diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.ru.xlf b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.ru.xlf new file mode 100644 index 000000000..20379b8a3 --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.ru.xlf @@ -0,0 +1,17 @@ + + + + + + Add Out/Ref Modifier + Добавить модификатор Out/Ref + + + + Remove Out/Ref Modifier + Удалить модификатор Out/Ref + + + + + \ No newline at end of file diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.tr.xlf b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.tr.xlf new file mode 100644 index 000000000..bbdd2c6e2 --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.tr.xlf @@ -0,0 +1,17 @@ + + + + + + Add Out/Ref Modifier + Out/Ref Değiştiricisi Ekle + + + + Remove Out/Ref Modifier + Out/Ref Değiştiricisini Kaldır + + + + + \ No newline at end of file diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.zh-Hans.xlf b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.zh-Hans.xlf new file mode 100644 index 000000000..01ee6e863 --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.zh-Hans.xlf @@ -0,0 +1,17 @@ + + + + + + Add Out/Ref Modifier + 添加 Out/Ref 修饰符 + + + + Remove Out/Ref Modifier + 删除 Out/Ref 修饰符 + + + + + \ No newline at end of file diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.zh-Hant.xlf b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.zh-Hant.xlf new file mode 100644 index 000000000..a549532d3 --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/Properties/xlf/Resources.zh-Hant.xlf @@ -0,0 +1,17 @@ + + + + + + Add Out/Ref Modifier + 新增 Out/Ref 修飾元 + + + + Remove Out/Ref Modifier + 移除 Out/Ref 修飾元 + + + + + \ No newline at end of file diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/RefOutModifier.CSharp.csproj b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/RefOutModifier.CSharp.csproj new file mode 100644 index 000000000..4c86ddaf3 --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/RefOutModifier.CSharp.csproj @@ -0,0 +1,25 @@ + + + + netstandard1.3 + latest + + + + + + True + True + Resources.resx + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/RefOutModifierCodeRefactoringProvider.cs b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/RefOutModifierCodeRefactoringProvider.cs new file mode 100644 index 000000000..ba413d70e --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/RefOutModifierCodeRefactoringProvider.cs @@ -0,0 +1,37 @@ +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Text; + +namespace Roslyn.Samples.AddOrRemoveRefOutModifier +{ + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(RefOutModifierCodeRefactoringProvider)), Shared] + internal class RefOutModifierCodeRefactoringProvider : CodeRefactoringProvider + { + public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + Document document = context.Document; + TextSpan textSpan = context.Span; + CancellationToken cancellationToken = context.CancellationToken; + + // shouldn't have selection + if (!textSpan.IsEmpty) + { + return; + } + + // get applicable actions + ApplicableActionFinder finder = new ApplicableActionFinder(document, textSpan.Start, cancellationToken); + (TextSpan span, CodeAction action) = await finder.GetSpanAndActionAsync().ConfigureAwait(false); + if (action == null || !span.IntersectsWith(textSpan.Start)) + { + return; + } + + context.RegisterRefactoring(action); + } + } +} diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/RemoveOutOrRefCodeAction.cs b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/RemoveOutOrRefCodeAction.cs new file mode 100644 index 000000000..85cefecb2 --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Implementation/RemoveOutOrRefCodeAction.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Text; +using RefOutModifier.Properties; + +namespace Roslyn.Samples.AddOrRemoveRefOutModifier +{ + internal class RemoveOutOrRefCodeAction : CodeAction + { + private readonly Document document; + private readonly SemanticModel semanticModel; + private readonly ArgumentSyntax argument; + private readonly IEnumerable parameters; + + public static bool Applicable(SemanticModel semanticModel, ArgumentSyntax argument, IEnumerable parameters) + { + BaseMethodDeclarationSyntax method = argument.AncestorAndSelf(); + if (method == null || + method.Body == null) + { + return false; + } + + if (argument.RefOrOutKeyword.Kind() == SyntaxKind.RefKeyword) + { + return true; + } + + Debug.Assert(argument.RefOrOutKeyword.Kind() == SyntaxKind.OutKeyword); + + SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(argument.Expression); + if (!(symbolInfo.Symbol != null && symbolInfo.Symbol.Kind == SymbolKind.Local)) + { + return true; + } + + // for local, make sure it is definitely assigned before removing "out" keyword + InvocationExpressionSyntax invocation = argument.AncestorAndSelf(); + if (invocation == null) + { + return false; + } + + Tuple range = GetStatementRangeForFlowAnalysis(method.Body, TextSpan.FromBounds(method.Body.OpenBraceToken.Span.End, invocation.Span.Start)); + DataFlowAnalysis dataFlow = semanticModel.AnalyzeDataFlow(range.Item1, range.Item2); + foreach (ISymbol symbol in dataFlow.AlwaysAssigned) + { + if (symbolInfo.Symbol == symbol) + { + return true; + } + } + + return false; + } + + private static Tuple GetStatementRangeForFlowAnalysis(SyntaxNode node, TextSpan textSpan) where T : SyntaxNode + { + T firstStatement = null; + T lastStatement = null; + + foreach (T stmt in node.DescendantNodesAndSelf().OfType()) + { + if (firstStatement == null && stmt.Span.Start >= textSpan.Start) + { + firstStatement = stmt; + } + + if (firstStatement != null && stmt.Span.End <= textSpan.End && stmt.Parent == firstStatement.Parent) + { + lastStatement = stmt; + } + } + + if (firstStatement == null || lastStatement == null) + { + return null; + } + + return new Tuple(firstStatement, lastStatement); + } + + public RemoveOutOrRefCodeAction( + Document document, + SemanticModel semanticModel, + ArgumentSyntax argument, + IEnumerable parameters) + { + this.document = document; + this.semanticModel = semanticModel; + this.argument = argument; + this.parameters = parameters; + } + + public override string Title + { + get { return Resources.RemoveOutOrRefTitle; } + } + + protected override async Task GetChangedDocumentAsync(CancellationToken cancellationToken) + { + Dictionary map = new Dictionary + { + { argument.RefOrOutKeyword, default } + }; + + SyntaxToken tokenBeforeArgumentModifier = argument.RefOrOutKeyword.GetPreviousToken(includeSkipped: true); + map.Add(tokenBeforeArgumentModifier, + tokenBeforeArgumentModifier.MergeTrailingTrivia(argument.RefOrOutKeyword) + .WithAdditionalAnnotations(Formatter.Annotation)); + + foreach (ParameterSyntax parameter in parameters) + { + SyntaxToken outOrRefModifier = parameter.Modifiers.FirstOrDefault(t => t.Kind() == SyntaxKind.OutKeyword || t.Kind() == SyntaxKind.RefKeyword); + if (outOrRefModifier.Kind() == SyntaxKind.None) + { + continue; + } + + map.Add(outOrRefModifier, default); + + SyntaxToken tokenBeforeParameterModifier = outOrRefModifier.GetPreviousToken(includeSkipped: true); + map.Add(tokenBeforeParameterModifier, tokenBeforeParameterModifier.MergeTrailingTrivia(outOrRefModifier) + .WithAdditionalAnnotations(Formatter.Annotation)); + } + + SyntaxNode root = (SyntaxNode)await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + SyntaxNode newRoot = root.ReplaceTokens(map.Keys, (o, n) => map[o]); + + return document.WithSyntaxRoot(newRoot); + } + } +} diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Vsix/RefOutModifier.CSharp.Vsix.csproj b/samples/CSharp/RefOutModifier/RefOutModifier.Vsix/RefOutModifier.CSharp.Vsix.csproj new file mode 100644 index 000000000..0c33c5e38 --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Vsix/RefOutModifier.CSharp.Vsix.csproj @@ -0,0 +1,22 @@ + + + + net472 + RefOutModifier.CSharp.Vsix + RefOutModifier.CSharp.Vsix + false + false + false + false + false + + + + + Designer + + + + + + \ No newline at end of file diff --git a/samples/CSharp/RefOutModifier/RefOutModifier.Vsix/source.extension.vsixmanifest b/samples/CSharp/RefOutModifier/RefOutModifier.Vsix/source.extension.vsixmanifest new file mode 100644 index 000000000..55f0b8b60 --- /dev/null +++ b/samples/CSharp/RefOutModifier/RefOutModifier.Vsix/source.extension.vsixmanifest @@ -0,0 +1,21 @@ + + + + + RefOutModifier + This is a sample code refactoring extension for the .NET Compiler Platform ("Roslyn"). + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SolutionExplorer/App.config b/samples/CSharp/SolutionExplorer/App.config new file mode 100644 index 000000000..731f6de6c --- /dev/null +++ b/samples/CSharp/SolutionExplorer/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SolutionExplorer/App.xaml b/samples/CSharp/SolutionExplorer/App.xaml new file mode 100644 index 000000000..8cf5d4c13 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/App.xaml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/samples/CSharp/SolutionExplorer/App.xaml.cs b/samples/CSharp/SolutionExplorer/App.xaml.cs new file mode 100644 index 000000000..4c013c096 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/App.xaml.cs @@ -0,0 +1,45 @@ +using System.Windows; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using MSBuildWorkspaceTester.Logging; +using MSBuildWorkspaceTester.Services; +using MSBuildWorkspaceTester.ViewModels; + +namespace MSBuildWorkspaceTester +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + protected override void OnStartup(StartupEventArgs e) + { + ServiceCollection serviceCollection = new ServiceCollection(); + + ConfigureServices(serviceCollection); + + ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(); + + MSBuildService msbuildService = serviceProvider.GetRequiredService(); + msbuildService.Initialize(); + + MainWindowViewModel mainWindowViewModel = new MainWindowViewModel(serviceProvider); + + MainWindow = mainWindowViewModel.CreateView(); + MainWindow.Show(); + } + + private static void ConfigureServices(IServiceCollection serviceCollection) + { + OutputService outputService = new OutputService(); + ILoggerFactory loggerFactory = new LoggerFactory() + .AddOutput(outputService); + + serviceCollection.AddSingleton(loggerFactory); + serviceCollection.AddSingleton(outputService); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + } + } +} diff --git a/samples/CSharp/SolutionExplorer/Behaviors/MouseDoubleClickBehaviors.cs b/samples/CSharp/SolutionExplorer/Behaviors/MouseDoubleClickBehaviors.cs new file mode 100644 index 000000000..c721d8e57 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Behaviors/MouseDoubleClickBehaviors.cs @@ -0,0 +1,65 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace MSBuildWorkspaceTester.Behaviors +{ + public static class MouseDoubleClickBehaviors + { + public static DependencyProperty CommandProperty = + DependencyProperty.RegisterAttached( + name: "Command", + propertyType: typeof(ICommand), + ownerType: typeof(MouseDoubleClickBehaviors), + defaultMetadata: new UIPropertyMetadata( + propertyChangedCallback: CommandChanged)); + + public static ICommand GetCommand(DependencyObject obj) + => (ICommand)obj.GetValue(CommandProperty); + + public static void SetCommand(DependencyObject obj, ICommand value) + => obj.SetValue(CommandProperty, value); + + public static DependencyProperty CommandParameterProperty = + DependencyProperty.RegisterAttached( + name: "CommandParameter", + propertyType: typeof(object), + ownerType: typeof(MouseDoubleClickBehaviors), + defaultMetadata: new UIPropertyMetadata( + defaultValue: null, + propertyChangedCallback: CommandParameterChanged)); + + private static void CommandParameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + } + + public static object GetCommandParameter(DependencyObject obj) + => obj.GetValue(CommandParameterProperty); + + public static void SetCommandParameter(DependencyObject obj, object value) + => obj.SetValue(CommandParameterProperty, value); + + private static void CommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + if (obj is Control control) + { + if (e.NewValue != null && e.OldValue == null) + { + control.MouseDoubleClick += OnMouseDoubleClick; + } + else if (e.NewValue == null && e.OldValue != null) + { + control.MouseDoubleClick -= OnMouseDoubleClick; + } + + void OnMouseDoubleClick(object sender, RoutedEventArgs args) + { + ICommand command = (ICommand)control.GetValue(CommandProperty); + object commandParameter = control.GetValue(CommandParameterProperty); + command.Execute(commandParameter); + } + } + } + } +} diff --git a/samples/CSharp/SolutionExplorer/Behaviors/TextBoxBehaviors.cs b/samples/CSharp/SolutionExplorer/Behaviors/TextBoxBehaviors.cs new file mode 100644 index 000000000..6eeb7a1ed --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Behaviors/TextBoxBehaviors.cs @@ -0,0 +1,40 @@ +using System.Windows; +using System.Windows.Controls; + +namespace MSBuildWorkspaceTester.Behaviors +{ + public static class TextBoxBehaviors + { + public static readonly DependencyProperty AlwaysScrollToEndProperty = + DependencyProperty.RegisterAttached( + name: "AlwaysScrollToEnd", + propertyType: typeof(bool), + ownerType: typeof(TextBoxBehaviors), + defaultMetadata: new UIPropertyMetadata( + defaultValue: false, + propertyChangedCallback: OnAlwaysScrollToEndChanged)); + + public static bool GetAlwaysScrollToEnd(DependencyObject obj) + => (bool)obj.GetValue(AlwaysScrollToEndProperty); + + public static void SetAlwaysScrollToEnd(DependencyObject obj, bool value) + => obj.SetValue(AlwaysScrollToEndProperty, value); + + private static void OnAlwaysScrollToEndChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + if (obj is TextBox textBox) + { + if ((bool)e.NewValue) + { + textBox.TextChanged += OnTextChanged; + } + else + { + textBox.TextChanged -= OnTextChanged; + } + + void OnTextChanged(object sender, TextChangedEventArgs args) => textBox.ScrollToEnd(); + } + } + } +} diff --git a/samples/CSharp/SolutionExplorer/Extensions.cs b/samples/CSharp/SolutionExplorer/Extensions.cs new file mode 100644 index 000000000..211d724ba --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Extensions.cs @@ -0,0 +1,12 @@ +using System.Windows; + +namespace MSBuildWorkspaceTester +{ + internal static class Extensions + { + public static T FindName(this FrameworkElement element, string name) + { + return (T)element.FindName(name); + } + } +} diff --git a/samples/CSharp/SolutionExplorer/Framework/NotifyTaskCompletion.cs b/samples/CSharp/SolutionExplorer/Framework/NotifyTaskCompletion.cs new file mode 100644 index 000000000..ffd55441c --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Framework/NotifyTaskCompletion.cs @@ -0,0 +1,67 @@ +using System; +using System.ComponentModel; +using System.Threading.Tasks; + +namespace MSBuildWorkspaceTester.Framework +{ + public class NotifyTaskCompletion : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + public Task Task { get; } + + public TResult Result => Task.Status == TaskStatus.RanToCompletion ? Task.Result : default; + public TaskStatus Status => Task.Status; + public bool IsCompleted => Task.IsCompleted; + public bool IsNotCompleted => !Task.IsCompleted; + public bool IsSuccessfullyCompleted => Task.Status == TaskStatus.RanToCompletion; + public bool IsCanceled => Task.IsCanceled; + public bool IsFaulted => Task.IsFaulted; + public AggregateException Exception => Task.Exception; + public Exception InnerException => Exception?.InnerException; + public string ErrorMessage => InnerException?.Message; + + public NotifyTaskCompletion(Task task) + { + Task = task; + if (!task.IsCompleted) + { + Task _ = WatchTaskAsync(task); + } + } + + private async Task WatchTaskAsync(Task task) + { + await task; + + PropertyChangedEventHandler handler = PropertyChanged; + if (handler == null) + { + return; + } + + void Notify(string propertyName) => handler(this, PropertyChangedEventArgsCache.GetEventArgs(propertyName)); + + Notify(nameof(Status)); + Notify(nameof(IsCompleted)); + Notify(nameof(IsNotCompleted)); + + if (task.IsCanceled) + { + Notify(nameof(IsCanceled)); + } + else if (task.IsFaulted) + { + Notify(nameof(IsFaulted)); + Notify(nameof(Exception)); + Notify(nameof(InnerException)); + Notify(nameof(ErrorMessage)); + } + else + { + Notify(nameof(IsSuccessfullyCompleted)); + Notify(nameof(Result)); + } + } + } +} diff --git a/samples/CSharp/SolutionExplorer/Framework/PropertyChangedEventArgsCache.cs b/samples/CSharp/SolutionExplorer/Framework/PropertyChangedEventArgsCache.cs new file mode 100644 index 000000000..687b51d02 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Framework/PropertyChangedEventArgsCache.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.ComponentModel; + +namespace MSBuildWorkspaceTester.Framework +{ + internal static class PropertyChangedEventArgsCache + { + private static readonly Dictionary s_eventArgsCache + = new Dictionary(); + + public static PropertyChangedEventArgs GetEventArgs(string propertyName) + { + lock (s_eventArgsCache) + { + if (!s_eventArgsCache.TryGetValue(propertyName, out PropertyChangedEventArgs eventArgs)) + { + eventArgs = new PropertyChangedEventArgs(propertyName); + s_eventArgsCache.Add(propertyName, eventArgs); + } + + return eventArgs; + } + } + } +} diff --git a/samples/CSharp/SolutionExplorer/Framework/ViewModel_CommandBindings.cs b/samples/CSharp/SolutionExplorer/Framework/ViewModel_CommandBindings.cs new file mode 100644 index 000000000..dd81576c1 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Framework/ViewModel_CommandBindings.cs @@ -0,0 +1,97 @@ +using System; +using System.Windows; +using System.Windows.Input; + +namespace MSBuildWorkspaceTester.Framework +{ + internal partial class ViewModel + { + private CommandBindingCollection _commandBindings; + private readonly object _gate = new object(); + + private void AddCommandBinding(CommandBinding binding) + { + CommandManager.RegisterClassCommandBinding(GetType(), binding); + + lock (_gate) + { + if (_commandBindings == null) + { + _commandBindings = new CommandBindingCollection(); + } + + _commandBindings.Add(binding); + } + } + + private ICommand RegisterCommand( + string text, string name, InputGesture[] inputGestures, + ExecutedRoutedEventHandler executed, + CanExecuteRoutedEventHandler canExecute) + { + RoutedUICommand command = new RoutedUICommand(text, name, GetType(), new InputGestureCollection(inputGestures)); + CommandBinding binding = new CommandBinding(command, executed, canExecute); + + AddCommandBinding(binding); + + return command; + } + + protected ICommand RegisterCommand(string text, string name, Action executed, Func canExecute, params InputGesture[] inputGestures) + { + return RegisterCommand(text, name, inputGestures, + executed: (s, e) => executed(), + canExecute: (s, e) => e.CanExecute = canExecute()); + } + + protected ICommand RegisterCommand(string text, string name, Action executed, Func canExecute, params InputGesture[] inputGestures) + { + T cast(object x) => x != null ? (T)x : default; + + return RegisterCommand(text, name, inputGestures, + executed: (s, e) => executed(cast(e.Parameter)), + canExecute: (s, e) => e.CanExecute = canExecute(cast(e.Parameter))); + } + + public static readonly DependencyProperty RegisterCommandsProperty = + DependencyProperty.RegisterAttached( + name: "RegisterCommands", + propertyType: typeof(ViewModel), + ownerType: typeof(ViewModel), + defaultMetadata: new PropertyMetadata( + defaultValue: null, + propertyChangedCallback: (dp, e) => + { + if (dp is UIElement element && e.NewValue is ViewModel viewModel) + { + lock (viewModel._gate) + { + if (viewModel._commandBindings != null) + { + element.CommandBindings.AddRange(viewModel._commandBindings); + } + } + } + })); + + public static ViewModel GetRegisterCommands(UIElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return element.GetValue(RegisterCommandsProperty) as ViewModel; + } + + public static void SetRegisterCommands(UIElement element, ViewModel value) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + element.SetValue(RegisterCommandsProperty, value); + } + } +} diff --git a/samples/CSharp/SolutionExplorer/Framework/ViewModel_INotifyPropertyChanged.cs b/samples/CSharp/SolutionExplorer/Framework/ViewModel_INotifyPropertyChanged.cs new file mode 100644 index 000000000..2632af8d0 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Framework/ViewModel_INotifyPropertyChanged.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace MSBuildWorkspaceTester.Framework +{ + internal abstract partial class ViewModel : INotifyPropertyChanged + { + private PropertyChangedEventHandler _propertyChangedHandler; + + protected void PropertyChanged(string propertyName) + { + PropertyChangedEventArgs eventArgs = PropertyChangedEventArgsCache.GetEventArgs(propertyName); + _propertyChangedHandler?.Invoke(this, eventArgs); + } + + protected void AllPropertiesChanged() + { + PropertyChanged(string.Empty); + } + + event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged + { + add { _propertyChangedHandler += value; } + remove { _propertyChangedHandler -= value; } + } + + protected void SetValue(ref T value, T newValue, [CallerMemberName] string propertyName = null) + { + if (!EqualityComparer.Default.Equals(value, newValue)) + { + value = newValue; + PropertyChanged(propertyName); + } + } + } +} diff --git a/samples/CSharp/SolutionExplorer/Framework/ViewModel`1.cs b/samples/CSharp/SolutionExplorer/Framework/ViewModel`1.cs new file mode 100644 index 000000000..68acf6558 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Framework/ViewModel`1.cs @@ -0,0 +1,40 @@ +using System; +using System.Windows; +using System.Windows.Controls; + +namespace MSBuildWorkspaceTester.Framework +{ + internal abstract class ViewModel : ViewModel + where TView : ContentControl + { + private readonly string _viewName; + + protected IServiceProvider ServiceProvider { get; } + protected TView View { get; private set; } + + protected ViewModel(string viewName, IServiceProvider serviceProvider) + { + _viewName = viewName ?? throw new ArgumentNullException(nameof(viewName)); + ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + } + + protected virtual void OnViewCreated(TView view) + { + // Decendents can override + } + + private string GetViewUriString() + => $"/{GetType().Assembly.GetName().Name};component/Views/{_viewName}.xaml"; + + public TView CreateView() + { + Uri uri = new Uri(GetViewUriString(), UriKind.Relative); + View = (TView)Application.LoadComponent(uri); + View.DataContext = this; + + OnViewCreated(View); + + return View; + } + } +} diff --git a/samples/CSharp/SolutionExplorer/Logging/Extensions.cs b/samples/CSharp/SolutionExplorer/Logging/Extensions.cs new file mode 100644 index 000000000..1689d55bb --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Logging/Extensions.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.Logging; +using MSBuildWorkspaceTester.Services; + +namespace MSBuildWorkspaceTester.Logging +{ + internal static class Extensions + { + public static ILoggerFactory AddOutput(this ILoggerFactory loggerFactory, OutputService outputService) + { + loggerFactory.AddProvider(new OutputLoggerProvider(outputService)); + return loggerFactory; + } + } +} diff --git a/samples/CSharp/SolutionExplorer/Logging/OutputLogger.cs b/samples/CSharp/SolutionExplorer/Logging/OutputLogger.cs new file mode 100644 index 000000000..f40847783 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Logging/OutputLogger.cs @@ -0,0 +1,31 @@ +using System; +using Microsoft.Extensions.Logging; +using MSBuildWorkspaceTester.Services; + +namespace MSBuildWorkspaceTester.Logging +{ + internal class OutputLogger : ILogger + { + private readonly OutputService _outputService; + + public OutputLogger(OutputService outputService) + { + _outputService = outputService ?? throw new ArgumentNullException(nameof(outputService)); + } + + private class NullDisposable : IDisposable + { + public void Dispose() { } + } + + public IDisposable BeginScope(TState state) => new NullDisposable(); + + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + string messageText = formatter(state, exception); + _outputService.WriteLine(messageText); + } + } +} diff --git a/samples/CSharp/SolutionExplorer/Logging/OutputLoggerProvider.cs b/samples/CSharp/SolutionExplorer/Logging/OutputLoggerProvider.cs new file mode 100644 index 000000000..e636f932a --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Logging/OutputLoggerProvider.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Logging; +using MSBuildWorkspaceTester.Services; + +namespace MSBuildWorkspaceTester.Logging +{ + internal class OutputLoggerProvider : ILoggerProvider + { + private readonly OutputService _outputService; + + public OutputLoggerProvider(OutputService outputService) + { + _outputService = outputService; + } + + public ILogger CreateLogger(string categoryName) + { + return new OutputLogger(_outputService); + } + + public void Dispose() { } + } +} diff --git a/samples/CSharp/SolutionExplorer/Properties/AssemblyInfo.cs b/samples/CSharp/SolutionExplorer/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..c16eabac9 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/samples/CSharp/SolutionExplorer/Properties/Resources.Designer.cs b/samples/CSharp/SolutionExplorer/Properties/Resources.Designer.cs new file mode 100644 index 000000000..d1e00dcfb --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace MSBuildWorkspaceTester.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MSBuildWorkspaceTester.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/Resources.resx b/samples/CSharp/SolutionExplorer/Properties/Resources.resx similarity index 100% rename from src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/Resources.resx rename to samples/CSharp/SolutionExplorer/Properties/Resources.resx diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Settings.Designer.cs b/samples/CSharp/SolutionExplorer/Properties/Settings.Designer.cs similarity index 81% rename from src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Settings.Designer.cs rename to samples/CSharp/SolutionExplorer/Properties/Settings.Designer.cs index 5974ed0c3..996bd756b 100644 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Settings.Designer.cs +++ b/samples/CSharp/SolutionExplorer/Properties/Settings.Designer.cs @@ -8,17 +8,21 @@ // //------------------------------------------------------------------------------ -namespace VisualBasic.Diagnostic.Test.My_Project { - - +namespace MSBuildWorkspaceTester.Properties +{ + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default { - get { + + public static Settings Default + { + get + { return defaultInstance; } } diff --git a/samples/CSharp/SolutionExplorer/Properties/Settings.settings b/samples/CSharp/SolutionExplorer/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.cs.xlf b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.cs.xlf new file mode 100644 index 000000000..99bb7f1d7 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.cs.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.de.xlf b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.de.xlf new file mode 100644 index 000000000..2b1fd700e --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.de.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.es.xlf b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.es.xlf new file mode 100644 index 000000000..a4f8872fd --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.es.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.fr.xlf b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.fr.xlf new file mode 100644 index 000000000..8231293ba --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.fr.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.it.xlf b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.it.xlf new file mode 100644 index 000000000..86dbb08ee --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.it.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.ja.xlf b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.ja.xlf new file mode 100644 index 000000000..737849421 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.ja.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.ko.xlf b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.ko.xlf new file mode 100644 index 000000000..4997d8516 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.ko.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.pl.xlf b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.pl.xlf new file mode 100644 index 000000000..b0181a3f5 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.pl.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.pt-BR.xlf b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.pt-BR.xlf new file mode 100644 index 000000000..56a8322a3 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.pt-BR.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.ru.xlf b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.ru.xlf new file mode 100644 index 000000000..7f33dd37b --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.ru.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.tr.xlf b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.tr.xlf new file mode 100644 index 000000000..cb11b99f6 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.tr.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.zh-Hans.xlf b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.zh-Hans.xlf new file mode 100644 index 000000000..d831e742d --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.zh-Hans.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.zh-Hant.xlf b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.zh-Hant.xlf new file mode 100644 index 000000000..a0c048744 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Properties/xlf/Resources.zh-Hant.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SolutionExplorer/Resources/Images.xaml b/samples/CSharp/SolutionExplorer/Resources/Images.xaml new file mode 100644 index 000000000..dd15acf5d --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Resources/Images.xaml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SolutionExplorer/Services/BaseService.cs b/samples/CSharp/SolutionExplorer/Services/BaseService.cs new file mode 100644 index 000000000..83f5736f4 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Services/BaseService.cs @@ -0,0 +1,20 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace MSBuildWorkspaceTester.Services +{ + internal abstract class BaseService + { + protected readonly IServiceProvider ServiceProvider; + protected readonly ILogger Logger; + + protected BaseService(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + + ILoggerFactory loggerFactory = serviceProvider.GetRequiredService(); + Logger = loggerFactory.CreateLogger(GetType()); + } + } +} diff --git a/samples/CSharp/SolutionExplorer/Services/MSBuildService.cs b/samples/CSharp/SolutionExplorer/Services/MSBuildService.cs new file mode 100644 index 000000000..7eca262d8 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Services/MSBuildService.cs @@ -0,0 +1,63 @@ +using System; +using System.Linq; +using Microsoft.Build.Locator; +using Microsoft.Extensions.Logging; + +namespace MSBuildWorkspaceTester.Services +{ + internal class MSBuildService : BaseService + { + public MSBuildService(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } + + public void Initialize() + { + VisualStudioInstance[] instances = GetVisualStudioInstances(); + if (instances.Length == 0) + { + return; + } + + VisualStudioInstance instance = instances[0]; + RegisterVisualStudioInstance(instance); + } + + private VisualStudioInstance[] GetVisualStudioInstances() + { + VisualStudioInstance[] instances = MSBuildLocator.QueryVisualStudioInstances().ToArray(); + if (instances.Length == 0) + { + Logger.LogError("No MSBuild instances found."); + return Array.Empty(); + } + + Logger.LogInformation("The following MSBuild instances have been discovered:"); + Logger.LogInformation(string.Empty); + + for (int i = 0; i < instances.Length; i++) + { + VisualStudioInstance instance = instances[i]; + Logger.LogInformation($" {i + 1}. {instance.Name} ({instance.Version})"); + } + + Logger.LogInformation(string.Empty); + + return instances; + } + + private void RegisterVisualStudioInstance(VisualStudioInstance instance) + { + MSBuildLocator.RegisterInstance(instance); + + Logger.LogInformation("Registered first MSBuild instance:"); + Logger.LogInformation(string.Empty); + Logger.LogInformation($" Name: {instance.Name}"); + Logger.LogInformation($" Version: {instance.Version}"); + Logger.LogInformation($" VisualStudioRootPath: {instance.VisualStudioRootPath}"); + Logger.LogInformation($" MSBuildPath: {instance.MSBuildPath}"); + Logger.LogInformation(string.Empty); + } + } +} diff --git a/samples/CSharp/SolutionExplorer/Services/OpenDocumentService.cs b/samples/CSharp/SolutionExplorer/Services/OpenDocumentService.cs new file mode 100644 index 000000000..bbbda9a56 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Services/OpenDocumentService.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Windows.Controls; +using Microsoft.CodeAnalysis; +using MSBuildWorkspaceTester.ViewModels; + +namespace MSBuildWorkspaceTester.Services +{ + internal class OpenDocumentService : BaseService + { + private readonly Dictionary _openDocumentTabs; + public ObservableCollection Pages { get; } + + public OpenDocumentService(IServiceProvider serviceProvider) + : base(serviceProvider) + { + _openDocumentTabs = new Dictionary(); + Pages = new ObservableCollection(); + } + + public void ActivateOrOpenDocument(DocumentViewModel documentViewModel) + { + if (!_openDocumentTabs.TryGetValue(documentViewModel.DocumentId, out PageViewModel pageViewModel)) + { + pageViewModel = new DocumentPageViewModel(ServiceProvider, documentViewModel); + _openDocumentTabs.Add(documentViewModel.DocumentId, pageViewModel); + Pages.Add(pageViewModel); + } + + SelectedPageChanged?.Invoke(this, pageViewModel); + } + + public event EventHandler SelectedPageChanged; + } +} diff --git a/samples/CSharp/SolutionExplorer/Services/OutputService.cs b/samples/CSharp/SolutionExplorer/Services/OutputService.cs new file mode 100644 index 000000000..f107002da --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Services/OutputService.cs @@ -0,0 +1,22 @@ +using System; +using System.Text; + +namespace MSBuildWorkspaceTester.Services +{ + internal class OutputService + { + private readonly StringBuilder _text = new StringBuilder(); + + public void WriteLine(string message) + { + _text.AppendLine(message); + + TextChanged?.Invoke(this, EventArgs.Empty); + } + + public string GetText() + => _text.ToString(); + + public event EventHandler TextChanged; + } +} diff --git a/samples/CSharp/SolutionExplorer/Services/WorkspaceService.cs b/samples/CSharp/SolutionExplorer/Services/WorkspaceService.cs new file mode 100644 index 000000000..6ca4c8f3a --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Services/WorkspaceService.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.MSBuild; +using Microsoft.Extensions.Logging; + +namespace MSBuildWorkspaceTester.Services +{ + internal class WorkspaceService : BaseService + { + public MSBuildWorkspace Workspace { get; } + + public WorkspaceService(IServiceProvider serviceProvider) + : base(serviceProvider) + { + Dictionary properties = new Dictionary + { + // 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 } + }; + + Workspace = MSBuildWorkspace.Create(properties); + Workspace.WorkspaceFailed += WorkspaceFailed; + } + + private void WorkspaceFailed(object sender, WorkspaceDiagnosticEventArgs e) + { + if (e.Diagnostic.Kind == WorkspaceDiagnosticKind.Failure) + { + Logger.LogError(e.Diagnostic.Message); + } + else + { + Logger.LogWarning(e.Diagnostic.Message); + } + } + + public async Task OpenSolutionAsync(string solutionFilePath) + { + LogHeader(); + + Stopwatch watch = Stopwatch.StartNew(); + Solution solution = await Workspace.OpenSolutionAsync(solutionFilePath, new LoaderProgress(Logger)); + + watch.Stop(); + Logger.LogInformation($"\r\nSolution opened: {watch.Elapsed:m\\:ss\\.fffffff}"); + } + + public async Task OpenProjectAsync(string projectFilePath) + { + LogHeader(); + + Stopwatch watch = Stopwatch.StartNew(); + Project project = await Workspace.OpenProjectAsync(projectFilePath, new LoaderProgress(Logger)); + + watch.Stop(); + Logger.LogInformation($"\r\nProject opened: {watch.Elapsed:m\\:ss\\.fffffff}"); + } + + private void LogHeader() + { + Logger.LogInformation("Operation Elapsed Time Project"); + } + + private class LoaderProgress : IProgress + { + private readonly ILogger _logger; + + public LoaderProgress(ILogger logger) + { + _logger = logger; + } + + public void Report(ProjectLoadProgress loadProgress) + { + string projectDisplay = Path.GetFileName(loadProgress.FilePath); + + if (loadProgress.TargetFramework != null) + { + projectDisplay += $" ({loadProgress.TargetFramework})"; + } + + _logger.LogInformation($"{loadProgress.Operation,-15} {loadProgress.ElapsedTime,-15:m\\:ss\\.fffffff} {projectDisplay}"); + } + } + } +} diff --git a/samples/CSharp/SolutionExplorer/SolutionExplorer.csproj b/samples/CSharp/SolutionExplorer/SolutionExplorer.csproj new file mode 100644 index 000000000..ac5c1ad26 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/SolutionExplorer.csproj @@ -0,0 +1,49 @@ + + + net461 + MSBuildWorkspaceTester + SolutionExplorer + true + WinExe + MSBuildWorkspaceTester + + MSBuildWorkspaceTester + + Copyright © 2017 + false + + + full + + + pdbonly + + + {B0474F4F-A6A9-4F10-BC49-CE2957201DBB} + MSBuildWorkspaceTester + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + true + latest + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SolutionExplorer/ViewModels/DocumentPageViewModel.cs b/samples/CSharp/SolutionExplorer/ViewModels/DocumentPageViewModel.cs new file mode 100644 index 000000000..53450f1c8 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/ViewModels/DocumentPageViewModel.cs @@ -0,0 +1,23 @@ +using System; +using System.Windows.Controls; +using MSBuildWorkspaceTester.Framework; + +namespace MSBuildWorkspaceTester.ViewModels +{ + internal class DocumentPageViewModel : PageViewModel + { + private readonly DocumentViewModel _documentViewModel; + + public NotifyTaskCompletion SourceText { get; } + + public DocumentPageViewModel(IServiceProvider serviceProvider, DocumentViewModel documentViewModel) + : base("DocumentView", serviceProvider) + { + _documentViewModel = documentViewModel; + + Caption = _documentViewModel.DisplayName; + + SourceText = new NotifyTaskCompletion(_documentViewModel.GetSourceTextAsync()); + } + } +} diff --git a/samples/CSharp/SolutionExplorer/ViewModels/DocumentViewModel.cs b/samples/CSharp/SolutionExplorer/ViewModels/DocumentViewModel.cs new file mode 100644 index 000000000..9c5dae529 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/ViewModels/DocumentViewModel.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; + +namespace MSBuildWorkspaceTester.ViewModels +{ + internal class DocumentViewModel : HierarchyItemViewModel + { + public DocumentId DocumentId { get; } + + public DocumentViewModel(Workspace workspace, DocumentId documentId) + : base(workspace, isExpanded: false) + { + DocumentId = documentId; + } + + private Document GetDocument() + => Workspace.CurrentSolution.GetDocument(DocumentId); + + protected override string GetDisplayName() + => GetDocument().Name; + + public string Language + => GetDocument().Project.Language; + + public async Task GetSourceTextAsync() + { + Microsoft.CodeAnalysis.Text.SourceText text = await GetDocument().GetTextAsync(); + return text.ToString(); + } + + public override int CompareTo(HierarchyItemViewModel other) + { + if (other is FolderViewModel || other is ReferencesFolderViewModel) + { + return 1; + } + + return base.CompareTo(other); + } + } +} diff --git a/samples/CSharp/SolutionExplorer/ViewModels/FolderViewModel.cs b/samples/CSharp/SolutionExplorer/ViewModels/FolderViewModel.cs new file mode 100644 index 000000000..6cb87cb6a --- /dev/null +++ b/samples/CSharp/SolutionExplorer/ViewModels/FolderViewModel.cs @@ -0,0 +1,35 @@ +using Microsoft.CodeAnalysis; + +namespace MSBuildWorkspaceTester.ViewModels +{ + internal class FolderViewModel : HierarchyItemViewModel + { + private readonly string _displayName; + + public FolderViewModel(Workspace workspace, string displayName) : base(workspace, isExpanded: false) + { + _displayName = displayName; + } + + protected override string GetDisplayName() + { + return _displayName; + } + + public override int CompareTo(HierarchyItemViewModel other) + { + if (other is ReferencesFolderViewModel) + { + return 1; + } + + if (other is DocumentViewModel) + { + return -1; + } + + return base.CompareTo(other); + } + + } +} diff --git a/samples/CSharp/SolutionExplorer/ViewModels/HierarchyItemViewModel.cs b/samples/CSharp/SolutionExplorer/ViewModels/HierarchyItemViewModel.cs new file mode 100644 index 000000000..01c58b40d --- /dev/null +++ b/samples/CSharp/SolutionExplorer/ViewModels/HierarchyItemViewModel.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.ObjectModel; +using Microsoft.CodeAnalysis; +using MSBuildWorkspaceTester.Framework; + +namespace MSBuildWorkspaceTester.ViewModels +{ + internal abstract class HierarchyItemViewModel : ViewModel, IComparable + { + private bool _isExpanded; + protected Workspace Workspace { get; } + private ObservableCollection _children; + public ReadOnlyObservableCollection Children { get; } + + protected HierarchyItemViewModel(Workspace workspace, bool isExpanded = true) + { + Workspace = workspace; + _isExpanded = isExpanded; + _children = new ObservableCollection(); + Children = new ReadOnlyObservableCollection(_children); + } + + public bool IsExpanded + { + get => _isExpanded; + set => SetValue(ref _isExpanded, value); + } + + public string DisplayName => GetDisplayName(); + + protected abstract string GetDisplayName(); + + public int BinarySearch(HierarchyItemViewModel value) + { + int low = 0; + int high = _children.Count - 1; + + while (low <= high) + { + int mid = low + ((high - low) / 2); + int comp = _children[mid].CompareTo(value); + + if (comp == 0) + { + return mid; + } + + if (comp < 0) + { + low = mid + 1; + } + else + { + high = mid - 1; + } + } + + return ~low; + } + + public void AddChild(HierarchyItemViewModel item) + { + int index = BinarySearch(item); + + if (index < 0) + { + _children.Insert(~index, item); + } + else + { + _children.Insert(index, item); + } + } + + public virtual int CompareTo(HierarchyItemViewModel other) + { + return StringComparer.OrdinalIgnoreCase.Compare(DisplayName, other.DisplayName); + } + } +} diff --git a/samples/CSharp/SolutionExplorer/ViewModels/LogViewModel.cs b/samples/CSharp/SolutionExplorer/ViewModels/LogViewModel.cs new file mode 100644 index 000000000..57cf732ef --- /dev/null +++ b/samples/CSharp/SolutionExplorer/ViewModels/LogViewModel.cs @@ -0,0 +1,23 @@ +using System; +using System.Windows.Controls; +using Microsoft.Extensions.DependencyInjection; +using MSBuildWorkspaceTester.Services; + +namespace MSBuildWorkspaceTester.ViewModels +{ + internal class LogViewModel : PageViewModel + { + private readonly OutputService _outputService; + + public LogViewModel(IServiceProvider serviceProvider) + : base("LogView", serviceProvider) + { + _outputService = serviceProvider.GetRequiredService(); + _outputService.TextChanged += delegate { PropertyChanged("OutputText"); }; + + Caption = "Log"; + } + + public string OutputText => _outputService.GetText(); + } +} diff --git a/samples/CSharp/SolutionExplorer/ViewModels/MainWindowViewModel.cs b/samples/CSharp/SolutionExplorer/ViewModels/MainWindowViewModel.cs new file mode 100644 index 000000000..d1bcb36f2 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.ObjectModel; +using System.IO; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Win32; +using MSBuildWorkspaceTester.Framework; +using MSBuildWorkspaceTester.Services; + +namespace MSBuildWorkspaceTester.ViewModels +{ + internal class MainWindowViewModel : ViewModel + { + private readonly ILogger _logger; + private readonly WorkspaceService _workspaceService; + private readonly OpenDocumentService _openDocumentService; + + public ICommand OpenProjectCommand { get; } + public ICommand OpenFileCommand { get; } + + public ObservableCollection Solution { get; } + public ObservableCollection Pages => _openDocumentService.Pages; + + public MainWindowViewModel(IServiceProvider serviceProvider) + : base("MainWindowView", serviceProvider) + { + _logger = serviceProvider.GetRequiredService().CreateLogger(); + + _workspaceService = serviceProvider.GetRequiredService(); + _openDocumentService = serviceProvider.GetRequiredService(); + + Solution = new ObservableCollection(); + Pages.Add(new LogViewModel(serviceProvider)); + + OpenProjectCommand = RegisterCommand( + text: "Open Project/Solution", + name: "Open Project/Solution", + executed: OpenProjectExecuted, + canExecute: CanOpenProjectExecute); + + OpenFileCommand = RegisterCommand( + text: "Open File", + name: "Open File", + executed: OpenFileExecuted, + canExecute: CanOpenFileExecute); + } + + protected override void OnViewCreated(Window view) + { + TabControl tabControl = view.FindName("Tabs"); + tabControl.SelectedIndex = 0; + + _openDocumentService.SelectedPageChanged += (_, page) => + { + tabControl.SelectedItem = page; + }; + } + + private bool CanOpenProjectExecute() => true; + + private async void OpenProjectExecuted() + { + OpenFileDialog dialog = new OpenFileDialog + { + Title = "Open Project or Solution", + Filter = "Supported Files (*.sln,*.csproj,*.vbproj)|*.sln;*.csproj;*.vbproj|" + + "Solution Files (*.sln)||*.sln|" + + "Project Files (*.csproj,*.vbproj)|*.csproj|*.vbproj|" + + "All Files (*.*)|*.*" + }; + + if (dialog.ShowDialog(View) == true) + { + string fileName = dialog.FileName; + string extension = Path.GetExtension(fileName); + + _logger.LogInformation($"\r\nLoading {fileName}...\r\n"); + + switch (extension) + { + case ".sln": + await _workspaceService.OpenSolutionAsync(fileName); + break; + + default: + await _workspaceService.OpenProjectAsync(fileName); + break; + } + + Solution.Clear(); + Solution.Add(new SolutionViewModel(_workspaceService.Workspace)); + } + } + + private bool CanOpenFileExecute(HierarchyItemViewModel viewModel) => true; + + private void OpenFileExecuted(HierarchyItemViewModel viewModel) + { + if (viewModel is DocumentViewModel documentViewModel) + { + _openDocumentService.ActivateOrOpenDocument(documentViewModel); + } + } + } +} diff --git a/samples/CSharp/SolutionExplorer/ViewModels/MetadataReferenceViewModel.cs b/samples/CSharp/SolutionExplorer/ViewModels/MetadataReferenceViewModel.cs new file mode 100644 index 000000000..0120322ee --- /dev/null +++ b/samples/CSharp/SolutionExplorer/ViewModels/MetadataReferenceViewModel.cs @@ -0,0 +1,26 @@ +using System.IO; +using Microsoft.CodeAnalysis; + +namespace MSBuildWorkspaceTester.ViewModels +{ + internal class MetadataReferenceViewModel : HierarchyItemViewModel + { + private readonly MetadataReference _metadataReference; + + public MetadataReferenceViewModel(Workspace workspace, MetadataReference metadataReference) + : base(workspace) + { + _metadataReference = metadataReference; + } + + protected override string GetDisplayName() + { + if (_metadataReference is PortableExecutableReference peReference) + { + return Path.GetFileNameWithoutExtension(peReference.FilePath); + } + + return _metadataReference.Display; + } + } +} diff --git a/samples/CSharp/SolutionExplorer/ViewModels/PageViewModel.cs b/samples/CSharp/SolutionExplorer/ViewModels/PageViewModel.cs new file mode 100644 index 000000000..ffa6f1fd6 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/ViewModels/PageViewModel.cs @@ -0,0 +1,61 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using MSBuildWorkspaceTester.Framework; + +namespace MSBuildWorkspaceTester.ViewModels +{ + internal abstract class PageViewModel : ViewModel + { + private string _caption; + private FrameworkElement _content; + + public string Caption + { + get => _caption; + protected set => SetValue(ref _caption, value); + } + + public FrameworkElement Content + { + get => _content; + protected set => SetValue(ref _content, value); + } + } + + internal abstract class PageViewModel : PageViewModel + where TContent : ContentControl + { + private readonly string _contentName; + + protected IServiceProvider ServiceProvider { get; } + + protected PageViewModel(string contentName, IServiceProvider serviceProvider) + { + _contentName = contentName ?? throw new ArgumentNullException(nameof(contentName)); + ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + + CreateContent(); + } + + protected virtual void OnContentCreated(TContent content) + { + // Decendents can override + } + + private string GetViewUriString() + => $"/{GetType().Assembly.GetName().Name};component/Views/{_contentName}.xaml"; + + public TContent CreateContent() + { + Uri uri = new Uri(GetViewUriString(), UriKind.Relative); + TContent content = (TContent)Application.LoadComponent(uri); + content.DataContext = this; + Content = content; + + OnContentCreated(content); + + return content; + } + } +} diff --git a/samples/CSharp/SolutionExplorer/ViewModels/ProjectReferenceViewModel.cs b/samples/CSharp/SolutionExplorer/ViewModels/ProjectReferenceViewModel.cs new file mode 100644 index 000000000..59633a5cf --- /dev/null +++ b/samples/CSharp/SolutionExplorer/ViewModels/ProjectReferenceViewModel.cs @@ -0,0 +1,21 @@ +using Microsoft.CodeAnalysis; + +namespace MSBuildWorkspaceTester.ViewModels +{ + internal class ProjectReferenceViewModel : HierarchyItemViewModel + { + private readonly ProjectReference _projectReference; + + public ProjectReferenceViewModel(Workspace workspace, ProjectReference projectReference) + : base(workspace) + { + _projectReference = projectReference; + } + + protected override string GetDisplayName() + => Workspace.CurrentSolution.GetProject(_projectReference.ProjectId).Name; + + public string Language + => Workspace.CurrentSolution.GetProject(_projectReference.ProjectId).Language; + } +} diff --git a/samples/CSharp/SolutionExplorer/ViewModels/ProjectViewModel.cs b/samples/CSharp/SolutionExplorer/ViewModels/ProjectViewModel.cs new file mode 100644 index 000000000..6f924716b --- /dev/null +++ b/samples/CSharp/SolutionExplorer/ViewModels/ProjectViewModel.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace MSBuildWorkspaceTester.ViewModels +{ + internal class ProjectViewModel : HierarchyItemViewModel + { + private readonly ProjectId _projectId; + private readonly Dictionary _folders; + + public ProjectViewModel(Workspace workspace, ProjectId projectId) + : base(workspace, isExpanded: false) + { + _projectId = projectId; + _folders = new Dictionary(StringComparer.OrdinalIgnoreCase); + + Solution solution = workspace.CurrentSolution; + Project project = solution.GetProject(projectId); + + foreach (DocumentId documentId in project.DocumentIds) + { + DocumentViewModel documentViewModel = new DocumentViewModel(workspace, documentId); + + IReadOnlyList folders = solution.GetDocument(documentId).Folders; + if (folders.Count > 0) + { + FolderViewModel folderViewModel = GetOrCreateFolder(workspace, folders); + folderViewModel.AddChild(documentViewModel); + } + else + { + AddChild(documentViewModel); + } + } + + // Add root folders + foreach (KeyValuePair folder in _folders) + { + if (!folder.Key.Contains('\\')) + { + AddChild(folder.Value); + } + } + + ReferencesFolderViewModel referencesFolderViewModel = null; + + // Add references + if (project.MetadataReferences.Count > 0) + { + referencesFolderViewModel = new ReferencesFolderViewModel(workspace); + + foreach (MetadataReference metadataReference in project.MetadataReferences) + { + MetadataReferenceViewModel metadataReferenceViewModel = new MetadataReferenceViewModel(workspace, metadataReference); + referencesFolderViewModel.AddChild(metadataReferenceViewModel); + } + } + + if (project.ProjectReferences.Any()) + { + if (referencesFolderViewModel == null) + { + referencesFolderViewModel = new ReferencesFolderViewModel(workspace); + } + + foreach (ProjectReference projectReference in project.ProjectReferences) + { + ProjectReferenceViewModel projectReferenceViewModel = new ProjectReferenceViewModel(workspace, projectReference); + referencesFolderViewModel.AddChild(projectReferenceViewModel); + } + } + + if (referencesFolderViewModel != null) + { + AddChild(referencesFolderViewModel); + } + } + + private FolderViewModel GetOrCreateFolder(Workspace workspace, IReadOnlyList folders) + { + string folderPath = string.Join(@"\", folders); + + if (_folders.TryGetValue(folderPath, out FolderViewModel folderViewModel)) + { + return folderViewModel; + } + + string currentFolderPath = folderPath; + + foreach (string folder in folders.Reverse()) + { + FolderViewModel newFolderViewModel = new FolderViewModel(workspace, folder); + + if (folderViewModel != null) + { + newFolderViewModel.AddChild(folderViewModel); + } + + folderViewModel = newFolderViewModel; + _folders.Add(currentFolderPath, folderViewModel); + + int lastSlashIndex = currentFolderPath.LastIndexOf('\\'); + if (lastSlashIndex < 0) + { + break; + } + + string parentFolderPath = currentFolderPath.Substring(0, lastSlashIndex); + + if (_folders.TryGetValue(parentFolderPath, out FolderViewModel parentFolderViewModel)) + { + parentFolderViewModel.AddChild(folderViewModel); + return folderViewModel; + } + + currentFolderPath = parentFolderPath; + } + + return _folders[folderPath]; + } + + protected override string GetDisplayName() + => Workspace.CurrentSolution.GetProject(_projectId).Name; + + public string Language + => Workspace.CurrentSolution.GetProject(_projectId).Language; + } +} diff --git a/samples/CSharp/SolutionExplorer/ViewModels/ReferencesFolderViewModel.cs b/samples/CSharp/SolutionExplorer/ViewModels/ReferencesFolderViewModel.cs new file mode 100644 index 000000000..41d3133c5 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/ViewModels/ReferencesFolderViewModel.cs @@ -0,0 +1,14 @@ +using Microsoft.CodeAnalysis; + +namespace MSBuildWorkspaceTester.ViewModels +{ + internal class ReferencesFolderViewModel : HierarchyItemViewModel + { + public ReferencesFolderViewModel(Workspace workspace) + : base(workspace, isExpanded: false) + { + } + + protected override string GetDisplayName() => "References"; + } +} diff --git a/samples/CSharp/SolutionExplorer/ViewModels/SolutionViewModel.cs b/samples/CSharp/SolutionExplorer/ViewModels/SolutionViewModel.cs new file mode 100644 index 000000000..84680f4d8 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/ViewModels/SolutionViewModel.cs @@ -0,0 +1,21 @@ +using System.IO; +using Microsoft.CodeAnalysis; + +namespace MSBuildWorkspaceTester.ViewModels +{ + internal class SolutionViewModel : HierarchyItemViewModel + { + public SolutionViewModel(Workspace workspace) + : base(workspace) + { + Solution solution = workspace.CurrentSolution; + foreach (ProjectId projectId in solution.ProjectIds) + { + AddChild(new ProjectViewModel(workspace, projectId)); + } + } + + protected override string GetDisplayName() + => Path.GetFileNameWithoutExtension(Workspace.CurrentSolution.FilePath); + } +} diff --git a/samples/CSharp/SolutionExplorer/Views/DocumentView.xaml b/samples/CSharp/SolutionExplorer/Views/DocumentView.xaml new file mode 100644 index 000000000..8a6c72fb1 --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Views/DocumentView.xaml @@ -0,0 +1,18 @@ + + + + + diff --git a/samples/CSharp/SolutionExplorer/Views/LogView.xaml b/samples/CSharp/SolutionExplorer/Views/LogView.xaml new file mode 100644 index 000000000..cc8e5e11f --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Views/LogView.xaml @@ -0,0 +1,17 @@ + + + + + diff --git a/samples/CSharp/SolutionExplorer/Views/MainWindowView.xaml b/samples/CSharp/SolutionExplorer/Views/MainWindowView.xaml new file mode 100644 index 000000000..3a5dbc44d --- /dev/null +++ b/samples/CSharp/SolutionExplorer/Views/MainWindowView.xaml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/CSharpGeneratedDemo.csproj b/samples/CSharp/SourceGenerators/GeneratedDemo/CSharpGeneratedDemo.csproj new file mode 100644 index 000000000..4b3054e9f --- /dev/null +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/CSharpGeneratedDemo.csproj @@ -0,0 +1,22 @@ + + + + Exe + netcoreapp3.1 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/Cars.csv b/samples/CSharp/SourceGenerators/GeneratedDemo/Cars.csv new file mode 100644 index 000000000..2e0547181 --- /dev/null +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/Cars.csv @@ -0,0 +1,3 @@ +Brand, Model, Year, cc +Fiat, Punto, 2008, 12.3 +Ford, Wagon, 1956, 20.3 \ No newline at end of file diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/Geometry.math b/samples/CSharp/SourceGenerators/GeneratedDemo/Geometry.math new file mode 100644 index 000000000..9ccca204c --- /dev/null +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/Geometry.math @@ -0,0 +1,9 @@ +AreaSquare(l) = pow(l, 2) +AreaRectangle(w, h) = w * h +AreaCircle(r) = pi * r * r +Quadratic(a, b, c) = {-b + sqrt(pow(b,2) - 4 * a * c)} / (2 * a) + +GoldenRatio = 1.61803 +GoldHarm(n) = GoldenRatio + 1 * ∑(i, 1, n, 1 / i) + +D(x', x'', y', y'') = sqrt(pow([x'-x''],2) + pow([y'-y''], 2)) diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/MainSettings.xmlsettings b/samples/CSharp/SourceGenerators/GeneratedDemo/MainSettings.xmlsettings new file mode 100644 index 000000000..457d6b508 --- /dev/null +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/MainSettings.xmlsettings @@ -0,0 +1,5 @@ + + + false + 1234 + \ No newline at end of file diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/People.csv b/samples/CSharp/SourceGenerators/GeneratedDemo/People.csv new file mode 100644 index 000000000..5179c3539 --- /dev/null +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/People.csv @@ -0,0 +1,3 @@ +Name, address, 11Age +"Luca Bol", "23 Bell Street", 90 +"john doe", "32 Carl street", 45 \ No newline at end of file diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/Program.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/Program.cs new file mode 100644 index 000000000..297582c51 --- /dev/null +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/Program.cs @@ -0,0 +1,29 @@ +using System; + +namespace GeneratedDemo +{ + class Program + { + static void Main(string[] args) + { + // Run the various scenarios + Console.WriteLine("Running HelloWorld:\n"); + UseHelloWorldGenerator.Run(); + + Console.WriteLine("\n\nRunning AutoNotify:\n"); + UseAutoNotifyGenerator.Run(); + + Console.WriteLine("\n\nRunning XmlSettings:\n"); + UseXmlSettingsGenerator.Run(); + + Console.WriteLine("\n\nRunning CsvGenerator:\n"); + UseCsvGenerator.Run(); + + Console.WriteLine("\n\nRunning MustacheGenerator:\n"); + UseMustacheGenerator.Run(); + + Console.WriteLine("\n\nRunning MathsGenerator:\n"); + UseMathsGenerator.Run(); + } + } +} diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs new file mode 100644 index 000000000..77e346c9a --- /dev/null +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs @@ -0,0 +1,39 @@ +using System; +using AutoNotify; + +namespace GeneratedDemo +{ + // The view model we'd like to augment + public partial class ExampleViewModel + { + [AutoNotify] + private string _text = "private field text"; + + [AutoNotify(PropertyName = "Count")] + private int _amount = 5; + } + + public static class UseAutoNotifyGenerator + { + public static void Run() + { + ExampleViewModel vm = new ExampleViewModel(); + + // we didn't explicitly create the 'Text' property, it was generated for us + string text = vm.Text; + Console.WriteLine($"Text = {text}"); + + // Properties can have differnt names generated based on the PropertyName argument of the attribute + int count = vm.Count; + Console.WriteLine($"Count = {count}"); + + // the viewmodel will automatically implement INotifyPropertyChanged + vm.PropertyChanged += (o, e) => Console.WriteLine($"Property {e.PropertyName} was changed"); + vm.Text = "abc"; + vm.Count = 123; + + // Try adding fields to the ExampleViewModel class above and tagging them with the [AutoNotify] attribute + // You'll see the matching generated properties visibile in IntelliSense in realtime + } + } +} diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseCsvGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseCsvGenerator.cs new file mode 100644 index 000000000..29be8efaf --- /dev/null +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseCsvGenerator.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using static System.Console; +using CSV; + +namespace GeneratedDemo +{ + class UseCsvGenerator + { + public static void Run() + { + WriteLine("## CARS"); + Cars.All.ToList().ForEach(c => WriteLine($"{c.Brand}\t{c.Model}\t{c.Year}\t{c.Cc}")); + WriteLine("\n## PEOPLE"); + People.All.ToList().ForEach(p => WriteLine($"{p.Name}\t{p.Address}\t{p._11Age}")); + } + } +} diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.cs new file mode 100644 index 000000000..000328660 --- /dev/null +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.cs @@ -0,0 +1,11 @@ +namespace GeneratedDemo +{ + public static class UseHelloWorldGenerator + { + public static void Run() + { + // The static call below is generated at build time, and will list the syntax trees used in the compilation + HelloWorldGenerated.HelloWorld.SayHello(); + } + } +} diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseMathsGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseMathsGenerator.cs new file mode 100644 index 000000000..2c8b7ecf0 --- /dev/null +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseMathsGenerator.cs @@ -0,0 +1,15 @@ +using static System.Console; +using Maths; + +namespace GeneratedDemo +{ + public static class UseMathsGenerator + { + public static void Run() + { + WriteLine($"The area of a (10, 5) rectangle is: {Formulas.AreaRectangle(10, 5)}"); + WriteLine($"The area of a (10) square is: {Formulas.AreaSquare(10)}"); + WriteLine($"The GoldHarmon of 3 is: {Formulas.GoldHarm(3)}"); + } + } +} diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseMustacheGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseMustacheGenerator.cs new file mode 100644 index 000000000..992d56800 --- /dev/null +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseMustacheGenerator.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using static System.Console; +using Mustache; +using static GeneratedDemo.UseMustacheGenerator; + +[assembly: Mustache("Lottery", t1, h1)] +[assembly: Mustache("HR", t2, h2)] +[assembly: Mustache("HTML", t3, h3)] +[assembly: Mustache("Section", t4, h4)] +[assembly: Mustache("NestedSection", t5, h5)] + +namespace GeneratedDemo +{ + class UseMustacheGenerator + { + public static void Run() + { + WriteLine(Mustache.Constants.Lottery); + WriteLine(Mustache.Constants.HR); + WriteLine(Mustache.Constants.HTML); + WriteLine(Mustache.Constants.Section); + WriteLine(Mustache.Constants.NestedSection); + } + + // Mustache templates and hashes from the manual at https://mustache.github.io/mustache.1.html... + public const string t1 = @" +Hello {{name}} +You have just won {{value}} dollars! +{{#in_ca}} +Well, {{taxed_value}} dollars, after taxes. +{{/in_ca}} +"; + public const string h1 = @" +{ + ""name"": ""Chris"", + ""value"": 10000, + ""taxed_value"": 5000, + ""in_ca"": true +} +"; + public const string t2 = @" +* {{name}} +* {{age}} +* {{company}} +* {{{company}}} +"; + public const string h2 = @" +{ + ""name"": ""Chris"", + ""company"": ""GitHub"" +} +"; + public const string t3 = @" +Shown +{{#person}} + Never shown! +{{/person}} +"; + public const string h3 = @" +{ + ""person"": false +} +"; + public const string t4 = @" +{{#repo}} + {{name}} +{{/repo}} +"; + public const string h4 = @" +{ + ""repo"": [ + { ""name"": ""resque"" }, + { ""name"": ""hub"" }, + { ""name"": ""rip"" } + ] +} +"; + public const string t5 = @" +{{#repo}} + {{name}} + {{#nested}} + NestedName: {{name}} + {{/nested}} +{{/repo}} +"; + public const string h5 = @" +{ + ""repo"": [ + { ""name"": ""resque"", ""nested"":[{""name"":""nestedResque""}] }, + { ""name"": ""hub"" }, + { ""name"": ""rip"" } + ] +} +"; + + } +} diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.cs new file mode 100644 index 000000000..213181e51 --- /dev/null +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.cs @@ -0,0 +1,27 @@ +using System; +using AutoSettings; + +namespace GeneratedDemo +{ + public static class UseXmlSettingsGenerator + { + public static void Run() + { + // This XmlSettings generator makes a static property in the XmlSettings class for each .xmlsettings file + + // here we have the 'Main' settings file from MainSettings.xmlsettings + // the name is determined by the 'name' attribute of the root settings element + XmlSettings.MainSettings main = XmlSettings.Main; + Console.WriteLine($"Reading settings from {main.GetLocation()}"); + + // settings are strongly typed and can be read directly from the static instance + bool firstRun = XmlSettings.Main.FirstRun; + Console.WriteLine($"Setting firstRun = {firstRun}"); + + int cacheSize = XmlSettings.Main.CacheSize; + Console.WriteLine($"Setting cacheSize = {cacheSize}"); + + // Try adding some keys to the settings file and see the settings become available to read from + } + } +} diff --git a/samples/CSharp/SourceGenerators/README.md b/samples/CSharp/SourceGenerators/README.md new file mode 100644 index 000000000..958cf03f6 --- /dev/null +++ b/samples/CSharp/SourceGenerators/README.md @@ -0,0 +1,35 @@ +🚧 Work In Progress +======== + +These samples are for an in-progress feature of Roslyn. As such they may change or break as the feature is developed, and no level of support is implied. + +For more infomation on the Source Generators feature, see the [design document](https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.md). + +Prerequisites +----- + +These samples require Visual Studio 16.6 or higher. + +Building the samples +----- +Open `SourceGenerators.sln` in Visual Studio or run `dotnet build` from the `\SourceGenerators` directory. + +Running the samples +----- + +The generators must be run as part of another build, as they inject source into the project being built. This repo contains a sample project `GeneratorDemo` that relies of the sample generators to add code to it's compilation. + +Run `GeneratedDemo` in Visual studio or run `dotnet run` from the `GeneratorDemo` directory. + +Using the samples in your project +----- + +You can add the sample generators to your own project by adding an item group containing an analyzer reference: + +```xml + + + +``` + +You may need to close and reopen the solution in Visual Studio for the change to take effect. diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs new file mode 100644 index 000000000..3e2bfa811 --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace SourceGeneratorSamples +{ + [Generator] + public class AutoNotifyGenerator : ISourceGenerator + { + private const string attributeText = @" +using System; +namespace AutoNotify +{ + [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] + [System.Diagnostics.Conditional(""AutoNotifyGenerator_DEBUG"")] + sealed class AutoNotifyAttribute : Attribute + { + public AutoNotifyAttribute() + { + } + public string PropertyName { get; set; } + } +} +"; + + + public void Initialize(GeneratorInitializationContext context) + { + // Register the attribute source + context.RegisterForPostInitialization((i) => i.AddSource("AutoNotifyAttribute", attributeText)); + + // Register a syntax receiver that will be created for each generation pass + context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); + } + + public void Execute(GeneratorExecutionContext context) + { + // retrieve the populated receiver + if (!(context.SyntaxContextReceiver is SyntaxReceiver receiver)) + return; + + // get the added attribute, and INotifyPropertyChanged + INamedTypeSymbol attributeSymbol = context.Compilation.GetTypeByMetadataName("AutoNotify.AutoNotifyAttribute"); + INamedTypeSymbol notifySymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged"); + + // group the fields by class, and generate the source + foreach (IGrouping group in receiver.Fields.GroupBy(f => f.ContainingType)) + { + string classSource = ProcessClass(group.Key, group.ToList(), attributeSymbol, notifySymbol, context); + context.AddSource($"{group.Key.Name}_autoNotify.cs", SourceText.From(classSource, Encoding.UTF8)); + } + } + + private string ProcessClass(INamedTypeSymbol classSymbol, List fields, ISymbol attributeSymbol, ISymbol notifySymbol, GeneratorExecutionContext context) + { + if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default)) + { + return null; //TODO: issue a diagnostic that it must be top level + } + + string namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); + + // begin building the generated source + StringBuilder source = new StringBuilder($@" +namespace {namespaceName} +{{ + public partial class {classSymbol.Name} : {notifySymbol.ToDisplayString()} + {{ +"); + + // if the class doesn't implement INotifyPropertyChanged already, add it + if (!classSymbol.Interfaces.Contains(notifySymbol)) + { + source.Append("public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;"); + } + + // create properties for each field + foreach (IFieldSymbol fieldSymbol in fields) + { + ProcessField(source, fieldSymbol, attributeSymbol); + } + + source.Append("} }"); + return source.ToString(); + } + + private void ProcessField(StringBuilder source, IFieldSymbol fieldSymbol, ISymbol attributeSymbol) + { + // get the name and type of the field + string fieldName = fieldSymbol.Name; + ITypeSymbol fieldType = fieldSymbol.Type; + + // get the AutoNotify attribute from the field, and any associated data + AttributeData attributeData = fieldSymbol.GetAttributes().Single(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default)); + TypedConstant overridenNameOpt = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "PropertyName").Value; + + string propertyName = chooseName(fieldName, overridenNameOpt); + if (propertyName.Length == 0 || propertyName == fieldName) + { + //TODO: issue a diagnostic that we can't process this field + return; + } + + source.Append($@" +public {fieldType} {propertyName} +{{ + get + {{ + return this.{fieldName}; + }} + + set + {{ + this.{fieldName} = value; + this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(nameof({propertyName}))); + }} +}} + +"); + + string chooseName(string fieldName, TypedConstant overridenNameOpt) + { + if (!overridenNameOpt.IsNull) + { + return overridenNameOpt.Value.ToString(); + } + + fieldName = fieldName.TrimStart('_'); + if (fieldName.Length == 0) + return string.Empty; + + if (fieldName.Length == 1) + return fieldName.ToUpper(); + + return fieldName.Substring(0, 1).ToUpper() + fieldName.Substring(1); + } + + } + + /// + /// Created on demand before each generation pass + /// + class SyntaxReceiver : ISyntaxContextReceiver + { + public List Fields { get; } = new List(); + + /// + /// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation + /// + public void OnVisitSyntaxNode(GeneratorSyntaxContext context) + { + // any field with at least one attribute is a candidate for property generation + if (context.Node is FieldDeclarationSyntax fieldDeclarationSyntax + && fieldDeclarationSyntax.AttributeLists.Count > 0) + { + foreach (VariableDeclaratorSyntax variable in fieldDeclarationSyntax.Declaration.Variables) + { + // Get the symbol being declared by the field, and keep it if its annotated + IFieldSymbol fieldSymbol = context.SemanticModel.GetDeclaredSymbol(variable) as IFieldSymbol; + if (fieldSymbol.GetAttributes().Any(ad => ad.AttributeClass.ToDisplayString() == "AutoNotify.AutoNotifyAttribute")) + { + Fields.Add(fieldSymbol); + } + } + } + } + } + } +} diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj new file mode 100644 index 000000000..a952bd32c --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj @@ -0,0 +1,31 @@ + + + + netstandard2.0 + 8.0 + + + + + + + + + + + + + + + + $(GetTargetPathDependsOn);GetDependencyTargetPaths + + + + + + + + + + diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs new file mode 100644 index 000000000..d25b5111e --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using NotVisualBasic.FileIO; + +#nullable enable + +// CsvTextFileParser from https://github.com/22222/CsvTextFieldParser adding suppression rules for default VS config + +namespace CsvGenerator +{ + [Generator] + public class CSVGenerator : ISourceGenerator + { + public enum CsvLoadType + { + Startup, + OnDemand + } + + // Guesses type of property for the object from the value of a csv field + public static string GetCsvFieldType(string exemplar) => exemplar switch + { + _ when bool.TryParse(exemplar, out _) => "bool", + _ when int.TryParse(exemplar, out _) => "int", + _ when double.TryParse(exemplar, out _) => "double", + _ => "string" + }; + + // Examines the header row and the first row in the csv file to gather all header types and names + // Also it returns the first row of data, because it must be read to figure out the types, + // As the CsvTextFieldParser cannot 'Peek' ahead of one line. If there is no first line, + // it consider all properties as strings. The generator returns an empty list of properly + // typed objects in such cas. If the file is completely empty, an error is generated. + public static (string[], string[], string[]?) ExtractProperties(CsvTextFieldParser parser) + { + string[]? headerFields = parser.ReadFields(); + if (headerFields == null) throw new Exception("Empty csv file!"); + + string[]? firstLineFields = parser.ReadFields(); + if (firstLineFields == null) + { + return (Enumerable.Repeat("string", headerFields.Length).ToArray(), headerFields, firstLineFields); + } + else + { + return (firstLineFields.Select(GetCsvFieldType).ToArray(), headerFields.Select(StringToValidPropertyName).ToArray(), firstLineFields); + } + } + + // Adds a class to the `CSV` namespace for each `csv` file passed in. The class has a static property + // named `All` that returns the list of strongly typed objects generated on demand at first access. + // There is the slight chance of a race condition in a multi-thread program, but the result is relatively benign + // , loading the collection multiple times instead of once. Measures could be taken to avoid that. + public static string GenerateClassFile(string className, string csvText, CsvLoadType loadTime, bool cacheObjects) + { + StringBuilder sb = new StringBuilder(); + using CsvTextFieldParser parser = new CsvTextFieldParser(new StringReader(csvText)); + + //// Usings + sb.Append(@" +#nullable enable +namespace CSV { + using System.Collections.Generic; + +"); + //// Class Definition + sb.Append($" public class {className} {{\n"); + + + if (loadTime == CsvLoadType.Startup) + { + sb.Append(@$" + static {className}() {{ var x = All; }} +"); + } + (string[] types, string[] names, string[]? fields) = ExtractProperties(parser); + int minLen = Math.Min(types.Length, names.Length); + + for (int i = 0; i < minLen; i++) + { + sb.AppendLine($" public {types[i]} {StringToValidPropertyName(names[i])} {{ get; set;}} = default!;"); + } + sb.Append("\n"); + + //// Loading data + sb.AppendLine($" static IEnumerable<{className}>? _all = null;"); + sb.Append($@" + public static IEnumerable<{className}> All {{ + get {{"); + + if (cacheObjects) sb.Append(@" + if(_all != null) + return _all; +"); + sb.Append(@$" + + List<{className}> l = new List<{className}>(); + {className} c; +"); + + // This awkwardness comes from having to pre-read one row to figure out the types of props. + do + { + if (fields == null) continue; + if (fields.Length < minLen) throw new Exception("Not enough fields in CSV file."); + + sb.AppendLine($" c = new {className}();"); + string value = ""; + for (int i = 0; i < minLen; i++) + { + // Wrap strings in quotes. + value = GetCsvFieldType(fields[i]) == "string" ? $"\"{fields[i].Trim().Trim(new char[] { '"' })}\"" : fields[i]; + sb.AppendLine($" c.{names[i]} = {value};"); + } + sb.AppendLine(" l.Add(c);"); + + fields = parser.ReadFields(); + } while (!(fields == null)); + + sb.AppendLine(" _all = l;"); + sb.AppendLine(" return l;"); + + // Close things (property, class, namespace) + sb.Append(" }\n }\n }\n}\n"); + return sb.ToString(); + + } + + + static string StringToValidPropertyName(string s) + { + s = s.Trim(); + s = char.IsLetter(s[0]) ? char.ToUpper(s[0]) + s.Substring(1) : s; + s = char.IsDigit(s.Trim()[0]) ? "_" + s : s; + s = new string(s.Select(ch => char.IsDigit(ch) || char.IsLetter(ch) ? ch : '_').ToArray()); + return s; + } + + static IEnumerable<(string, string)> SourceFilesFromAdditionalFile(CsvLoadType loadTime, bool cacheObjects, AdditionalText file) + { + string className = Path.GetFileNameWithoutExtension(file.Path); + string csvText = file.GetText()!.ToString(); + return new (string, string)[] { (className, GenerateClassFile(className, csvText, loadTime, cacheObjects)) }; + } + + static IEnumerable<(string, string)> SourceFilesFromAdditionalFiles(IEnumerable<(CsvLoadType loadTime, bool cacheObjects, AdditionalText file)> pathsData) + => pathsData.SelectMany(d => SourceFilesFromAdditionalFile(d.loadTime, d.cacheObjects, d.file)); + + static IEnumerable<(CsvLoadType, bool, AdditionalText)> GetLoadOptions(GeneratorExecutionContext context) + { + foreach (AdditionalText file in context.AdditionalFiles) + { + if (Path.GetExtension(file.Path).Equals(".csv", StringComparison.OrdinalIgnoreCase)) + { + // are there any options for it? + context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.CsvLoadType", out string? loadTimeString); + Enum.TryParse(loadTimeString, ignoreCase: true, out CsvLoadType loadType); + + context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.CacheObjects", out string? cacheObjectsString); + bool.TryParse(cacheObjectsString, out bool cacheObjects); + + yield return (loadType, cacheObjects, file); + } + } + } + + public void Execute(GeneratorExecutionContext context) + { + IEnumerable<(CsvLoadType, bool, AdditionalText)> options = GetLoadOptions(context); + IEnumerable<(string, string)> nameCodeSequence = SourceFilesFromAdditionalFiles(options); + foreach ((string name, string code) in nameCodeSequence) + context.AddSource($"Csv_{name}", SourceText.From(code, Encoding.UTF8)); + } + + public void Initialize(GeneratorInitializationContext context) + { + } + } +} +#pragma warning restore IDE0008 // Use explicit type diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.props b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.props new file mode 100644 index 000000000..0741032bb --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.props @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs new file mode 100644 index 000000000..d3f099527 --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace SourceGeneratorSamples +{ + [Generator] + public class HelloWorldGenerator : ISourceGenerator + { + public void Execute(GeneratorExecutionContext context) + { + // begin creating the source we'll inject into the users compilation + StringBuilder sourceBuilder = new StringBuilder(@" +using System; +namespace HelloWorldGenerated +{ + public static class HelloWorld + { + public static void SayHello() + { + Console.WriteLine(""Hello from generated code!""); + Console.WriteLine(""The following syntax trees existed in the compilation that created this program:""); +"); + + // using the context, get a list of syntax trees in the users compilation + IEnumerable syntaxTrees = context.Compilation.SyntaxTrees; + + // add the filepath of each tree to the class we're building + foreach (SyntaxTree tree in syntaxTrees) + { + sourceBuilder.AppendLine($@"Console.WriteLine(@"" - {tree.FilePath}"");"); + } + + // finish creating the source to inject + sourceBuilder.Append(@" + } + } +}"); + + // inject the created source into the users compilation + context.AddSource("helloWorldGenerated", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); + } + + public void Initialize(GeneratorInitializationContext context) + { + // No initialization required + } + } +} diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs new file mode 100644 index 000000000..7cf2c1676 --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs @@ -0,0 +1,470 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using static System.Console; +using Tokens = System.Collections.Generic.IEnumerable; +using SymTable = System.Collections.Generic.HashSet; +using System.Linq; +using Microsoft.CodeAnalysis; +using System.IO; +using Microsoft.CodeAnalysis.Text; +using System.Diagnostics; + +#pragma warning disable IDE0008 // Use explicit type + +namespace MathsGenerator { + public enum TokenType { + Number, + Identifier, + Operation, + OpenParens, + CloseParens, + Equal, + EOL, + EOF, + Spaces, + Comma, + Sum, + None + } + + public struct Token { + public TokenType Type; + public string Value; + public int Line; + public int Column; + } + + public static class Lexer { + + public static void PrintTokens(IEnumerable tokens) { + foreach (var token in tokens) { + WriteLine($"{token.Line}, {token.Column}, {token.Type}, {token.Value}"); + } + } + + static (TokenType, string)[] tokenStrings = { + (TokenType.EOL, @"(\r\n|\r|\n)"), + (TokenType.Spaces, @"\s+"), + (TokenType.Number, @"[+-]?((\d+\.?\d*)|(\.\d+))"), + (TokenType.Identifier, @"[_a-zA-Z][`'""_a-zA-Z0-9]*"), + (TokenType.Operation, @"[\+\-/\*]"), + (TokenType.OpenParens, @"[([{]"), + (TokenType.CloseParens, @"[)\]}]"), + (TokenType.Equal, @"="), + (TokenType.Comma, @","), + (TokenType.Sum, @"∑") + }; + + static IEnumerable<(TokenType, Regex)> tokenExpressions = + tokenStrings.Select( + t => (t.Item1, new Regex($"^{t.Item2}", RegexOptions.Compiled | RegexOptions.Singleline))); + + // Can be optimized with spans to avoid so many allocations ... + static public Tokens Tokenize(string source) { + var currentLine = 1; + var currentColumn = 1; + + while (source.Length > 0) { + + var matchLength = 0; + var tokenType = TokenType.None; + var value = ""; + + foreach (var (type, rule) in tokenExpressions) { + var match = rule.Match(source); + if(match.Success) { + matchLength = match.Length; + tokenType = type; + value = match.Value; + break; + } + } + + if (matchLength == 0) { + + throw new Exception($"Unrecognized symbol '{source[currentLine - 1]}' at index {currentLine - 1} (line {currentLine}, column {currentColumn})."); + + } else { + + if(tokenType != TokenType.Spaces) + yield return new Token { + Type = tokenType, + Value = value, + Line = currentLine, + Column = currentColumn + }; + + currentColumn += matchLength; + if(tokenType == TokenType.EOL) { + currentLine += 1; + currentColumn = 0; + } + + source = source.Substring(matchLength); + } + } + + yield return new Token { + Type = TokenType.EOF, + Line = currentLine, + Column = currentColumn + }; + } + } + + /* EBNF for the language + lines = {line} EOF + line = {EOL} identifier [lround args rround] equal expr EOL {EOL} + args = identifier {comma identifier} + expr = [plus|minus] term { (plus|minus) term } + term = factor { (times|divide) factor }; + factor = number | var | func | sum | matrix | lround expr rround; + var = identifier; + func = identifier lround expr {comma expr} rround; + sum = ∑ lround identifier comma expr comma expr comma expr rround; + */ + public static class Parser { + + + public static string Parse(Tokens tokens) { + var globalSymbolTable = new SymTable(); + var symbolTable = new SymTable(); + var buffer = new StringBuilder(); + + var en = tokens.GetEnumerator(); + en.MoveNext(); + + buffer = Lines(new Context { + tokens = en, + globalSymbolTable = globalSymbolTable, + symbolTable = symbolTable, + buffer = buffer + }); + return buffer.ToString(); + + } + + private readonly static string Preamble = @" +using static System.Math; +using static Maths.FormulaHelpers; + +namespace Maths { + + public static partial class Formulas { +"; + private readonly static string Ending = @" + } +}"; + + private struct Context { + public IEnumerator tokens; + public SymTable globalSymbolTable; + public SymTable symbolTable; + public StringBuilder buffer; + } + + private static StringBuilder Error(Token token, TokenType type, string value = "") => + throw new Exception($"Expected {type} {(value == "" ? "" : $" with {token.Value}")} at {token.Line},{token.Column} Instead found {token.Type} with value {token.Value}"); + + static HashSet validFunctions = + new HashSet(typeof(System.Math).GetMethods().Select(m => m.Name.ToLower())); + + static Dictionary replacementStrings = new Dictionary { + {"'''", "Third" }, {"''", "Second" }, {"'", "Prime"} + }; + + private static StringBuilder EmitIdentifier(Context ctx, Token token) { + var val = token.Value; + + if(val == "pi") { + ctx.buffer.Append("PI"); // Doesn't follow pattern + return ctx.buffer; + } + + if(validFunctions.Contains(val)) { + ctx.buffer.Append(char.ToUpper(val[0]) + val.Substring(1)); + return ctx.buffer; + } + + string id = token.Value; + if(ctx.globalSymbolTable.Contains(token.Value) || + ctx.symbolTable.Contains(token.Value)) { + foreach (var r in replacementStrings) { + id = id.Replace(r.Key, r.Value); + } + return ctx.buffer.Append(id); + } else { + throw new Exception($"{token.Value} not a known identifier or function."); + } + } + + private static StringBuilder Emit(Context ctx, Token token) => token.Type switch + { + TokenType.EOL => ctx.buffer.Append("\n"), + TokenType.CloseParens => ctx.buffer.Append(')'), // All parens become rounded + TokenType.OpenParens => ctx.buffer.Append('('), + TokenType.Equal => ctx.buffer.Append("=>"), + TokenType.Comma => ctx.buffer.Append(token.Value), + + // Identifiers are normalized and checked for injection attacks + TokenType.Identifier => EmitIdentifier(ctx, token), + TokenType.Number => ctx.buffer.Append(token.Value), + TokenType.Operation => ctx.buffer.Append(token.Value), + TokenType.Sum => ctx.buffer.Append("MySum"), + _ => Error(token, TokenType.None) + }; + + private static bool Peek(Context ctx, TokenType type, string value = "") { + var token = ctx.tokens.Current; + + return (token.Type == type && value == "") || + (token.Type == type && value == token.Value); + } + private static Token NextToken(Context ctx) { + + var token = ctx.tokens.Current; + ctx.tokens.MoveNext(); + return token; + } + private static void Consume(Context ctx, TokenType type, string value = "") { + + var token = NextToken(ctx); + + if((token.Type == type && value == "") || + (token.Type == type && value == token.Value)) { + + ctx.buffer.Append(" "); + Emit(ctx, token); + } else { + Error(token, type, value); + } + } + + private static StringBuilder Lines(Context ctx) { + // lines = {line} EOF + + ctx.buffer.Append(Preamble); + + while(!Peek(ctx, TokenType.EOF)) + Line(ctx); + + ctx.buffer.Append(Ending); + + return ctx.buffer; + } + + private static void AddGlobalSymbol(Context ctx) { + var token = ctx.tokens.Current; + if(Peek(ctx, TokenType.Identifier)) { + ctx.globalSymbolTable.Add(token.Value); + } else { + Error(token, TokenType.Identifier); + } + } + private static void AddSymbol(Context ctx) { + var token = ctx.tokens.Current; + if(Peek(ctx, TokenType.Identifier)) { + ctx.symbolTable.Add(token.Value); + } else { + Error(token, TokenType.Identifier); + } + } + private static void Line(Context ctx) { + // line = {EOL} identifier [lround args rround] equal expr EOL {EOL} + + ctx.symbolTable.Clear(); + + while(Peek(ctx, TokenType.EOL)) + Consume(ctx, TokenType.EOL); + + ctx.buffer.Append("\tpublic static double "); + + AddGlobalSymbol(ctx); + Consume(ctx, TokenType.Identifier); + + if(Peek(ctx, TokenType.OpenParens, "(")) { + Consume(ctx, TokenType.OpenParens, "("); // Just round parens + Args(ctx); + Consume(ctx, TokenType.CloseParens, ")"); + } + + Consume(ctx, TokenType.Equal); + Expr(ctx); + ctx.buffer.Append(" ;"); + + Consume(ctx, TokenType.EOL); + + while(Peek(ctx, TokenType.EOL)) + Consume(ctx, TokenType.EOL); + } + private static void Args(Context ctx) { + // args = identifier {comma identifier} + // It doesn't make sense for a math function to have zero args (I think) + + ctx.buffer.Append("double "); + AddSymbol(ctx); + Consume(ctx, TokenType.Identifier); + + while(Peek(ctx, TokenType.Comma)) { + Consume(ctx, TokenType.Comma); + ctx.buffer.Append("double "); + AddSymbol(ctx); + Consume(ctx, TokenType.Identifier); + } + } + private static Func IsOp = (ctx, op) + => Peek(ctx, TokenType.Operation, op); + private static Action ConsOp = (ctx, op) + => Consume(ctx, TokenType.Operation, op); + + private static void Expr(Context ctx) { + // expr = [plus|minus] term { (plus|minus) term } + + if(IsOp(ctx, "+")) ConsOp(ctx, "+"); + if(IsOp(ctx, "-")) ConsOp(ctx, "-"); + + Term(ctx); + + while(IsOp(ctx, "+") || IsOp(ctx, "-")) { + + if(IsOp(ctx, "+")) ConsOp(ctx, "+"); + if(IsOp(ctx, "-")) ConsOp(ctx, "-"); + + Term(ctx); + } + } + private static void Term(Context ctx) { + // term = factor { (times|divide) factor }; + Factor(ctx); + + while(IsOp(ctx, "*") || IsOp(ctx, "/")) { + if(IsOp(ctx, "*")) ConsOp(ctx, "*"); + if(IsOp(ctx, "/")) ConsOp(ctx, "/"); + + Term(ctx); + } + } + private static void Factor(Context ctx) { + // factor = number | var | func | lround expr rround; + if(Peek(ctx, TokenType.Number)) { + Consume(ctx, TokenType.Number); + return; + } + + if(Peek(ctx, TokenType.Identifier)) { + Consume(ctx, TokenType.Identifier); // Is either var or func + if(Peek(ctx, TokenType.OpenParens, "(")) { // Is Func, but we already consumed its name + Funct(ctx); + } + return; + } + if(Peek(ctx, TokenType.Sum)) { + Sum(ctx); + return; + } + // Must be a parenthesized expression + Consume(ctx, TokenType.OpenParens); + Expr(ctx); + Consume(ctx, TokenType.CloseParens); + } + private static void Sum(Context ctx) { + // sum = ∑ lround identifier comma expr1 comma expr2 comma expr3 rround; + // TODO: differentiate in the language between integer and double, but complicated for a sample. + Consume(ctx, TokenType.Sum); + Consume(ctx, TokenType.OpenParens, "("); + + AddSymbol(ctx); + var varName = NextToken(ctx).Value; + NextToken(ctx); // consume the first comma without emitting it + + ctx.buffer.Append("(int)"); + Expr(ctx); // Start index + Consume(ctx, TokenType.Comma); + + ctx.buffer.Append("(int)"); + Expr(ctx); // End index + Consume(ctx, TokenType.Comma); + + ctx.buffer.Append($"{varName} => "); // It needs to be a lambda + + Expr(ctx); // expr to evaluate at each iteration + + Consume(ctx, TokenType.CloseParens, ")"); + } + private static void Funct(Context ctx) { + // func = identifier lround expr {comma expr} rround; + Consume(ctx, TokenType.OpenParens, "("); + Expr(ctx); + while(Peek(ctx, TokenType.Comma)) { + Consume(ctx, TokenType.Comma); + Expr(ctx); + } + Consume(ctx, TokenType.CloseParens, ")"); + } + } + + [Generator] + public class MathsGenerator : ISourceGenerator + { + private const string libraryCode = @" +using System.Linq; +using System; +using System.Collections.Generic; + +namespace Maths { + public static class FormulaHelpers { + + public static IEnumerable ConvertToDouble(IEnumerable col) + { + foreach (var s in col) + yield return (double) s; + } + + public static double MySum(int start, int end, Func f) => + Enumerable.Sum(ConvertToDouble(Enumerable.Range(start, end - start)), f); + } +} +"; + + public void Execute(GeneratorExecutionContext context) + { + foreach (AdditionalText file in context.AdditionalFiles) + { + if (Path.GetExtension(file.Path).Equals(".math", StringComparison.OrdinalIgnoreCase)) + { + // Load formulas from .math files + var mathText = file.GetText(); + var mathString = ""; + + if(mathText != null) + { + mathString = mathText.ToString(); + } + else + { + throw new Exception($"Cannot load file {file.Path}"); + } + + // Get name of generated namespace from file name + string fileName = Path.GetFileNameWithoutExtension(file.Path); + + // Parse and gen the formulas functions + var tokens = Lexer.Tokenize(mathString); + var code = Parser.Parse(tokens); + + var codeFileName = $@"{fileName}.cs"; + + context.AddSource(codeFileName, SourceText.From(code, Encoding.UTF8)); + } + } + } + + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForPostInitialization((pi) => pi.AddSource("__MathLibrary__.cs", libraryCode)); + } + } +} + diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs new file mode 100644 index 000000000..51766e7c7 --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +#nullable enable + +namespace Mustache +{ + [Generator] + public class MustacheGenerator : ISourceGenerator + { + private const string attributeSource = @" + [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple=true)] + internal sealed class MustacheAttribute: System.Attribute + { + public string Name { get; } + public string Template { get; } + public string Hash { get; } + public MustacheAttribute(string name, string template, string hash) + => (Name, Template, Hash) = (name, template, hash); + } +"; + + public void Execute(GeneratorExecutionContext context) + { + SyntaxReceiver rx = (SyntaxReceiver)context.SyntaxContextReceiver!; + foreach ((string name, string template, string hash) in rx.TemplateInfo) + { + string source = SourceFileFromMustachePath(name, template, hash); + context.AddSource($"Mustache{name}", source); + } + } + + static string SourceFileFromMustachePath(string name, string template, string hash) + { + Func tree = HandlebarsDotNet.Handlebars.Compile(template); + object @object = Newtonsoft.Json.JsonConvert.DeserializeObject(hash); + string mustacheText = tree(@object); + + StringBuilder sb = new StringBuilder(); + sb.Append($@" +namespace Mustache {{ + + public static partial class Constants {{ + + public const string {name} = @""{mustacheText.Replace("\"", "\"\"")}""; + }} +}} +"); + return sb.ToString(); + } + + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForPostInitialization((pi) => pi.AddSource("Mustache_MainAttributes__", attributeSource)); + context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); + } + + class SyntaxReceiver : ISyntaxContextReceiver + { + public List<(string name, string template, string hash)> TemplateInfo = new List<(string name, string template, string hash)>(); + + public void OnVisitSyntaxNode(GeneratorSyntaxContext context) + { + // find all valid mustache attributes + if (context.Node is AttributeSyntax attrib + && attrib.ArgumentList?.Arguments.Count == 3 + && context.SemanticModel.GetTypeInfo(attrib).Type?.ToDisplayString() == "MustacheAttribute") + { + string name = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[0].Expression).ToString(); + string template = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[1].Expression).ToString(); + string hash = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[2].Expression).ToString(); + + TemplateInfo.Add((name, template, hash)); + } + } + } + } +} diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs new file mode 100644 index 000000000..7854de5ef --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs @@ -0,0 +1,99 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; + +namespace Analyzer1 +{ + [Generator] + public class SettingsXmlGenerator : ISourceGenerator + { + public void Execute(GeneratorExecutionContext context) + { + // Using the context, get any additional files that end in .xmlsettings + IEnumerable settingsFiles = context.AdditionalFiles.Where(at => at.Path.EndsWith(".xmlsettings")); + foreach (AdditionalText settingsFile in settingsFiles) + { + ProcessSettingsFile(settingsFile, context); + } + } + + private void ProcessSettingsFile(AdditionalText xmlFile, GeneratorExecutionContext context) + { + // try and load the settings file + XmlDocument xmlDoc = new XmlDocument(); + string text = xmlFile.GetText(context.CancellationToken).ToString(); + try + { + xmlDoc.LoadXml(text); + } + catch + { + //TODO: issue a diagnostic that says we couldn't parse it + return; + } + + + // create a class in the XmlSetting class that represnts this entry, and a static field that contains a singleton instance. + string fileName = Path.GetFileName(xmlFile.Path); + string name = xmlDoc.DocumentElement.GetAttribute("name"); + + StringBuilder sb = new StringBuilder($@" +namespace AutoSettings +{{ + using System; + using System.Xml; + + public partial class XmlSettings + {{ + + public static {name}Settings {name} {{ get; }} = new {name}Settings(""{fileName}""); + + public class {name}Settings + {{ + + XmlDocument xmlDoc = new XmlDocument(); + + private string fileName; + + public string GetLocation() => fileName; + + internal {name}Settings(string fileName) + {{ + this.fileName = fileName; + xmlDoc.Load(fileName); + }} +"); + + for(int i = 0; i < xmlDoc.DocumentElement.ChildNodes.Count; i++) + { + XmlElement setting = (XmlElement)xmlDoc.DocumentElement.ChildNodes[i]; + string settingName = setting.GetAttribute("name"); + string settingType = setting.GetAttribute("type"); + + sb.Append($@" + +public {settingType} {settingName} +{{ + get + {{ + return ({settingType}) Convert.ChangeType(((XmlElement)xmlDoc.DocumentElement.ChildNodes[{i}]).InnerText, typeof({settingType})); + }} +}} +"); + } + + sb.Append("} } }"); + + context.AddSource($"Settings_{name}", SourceText.From(sb.ToString(), Encoding.UTF8)); + } + + public void Initialize(GeneratorInitializationContext context) + { + } + } +} diff --git a/samples/CSharp/SourceGenerators/SourceGenerators.sln b/samples/CSharp/SourceGenerators/SourceGenerators.sln new file mode 100644 index 000000000..1732a9c41 --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGenerators.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30022.13 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGeneratorSamples", "SourceGeneratorSamples\CSharpSourceGeneratorSamples.csproj", "{B452269D-856C-4FE6-8900-3D81461AF864}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeneratedDemo", "GeneratedDemo\CSharpGeneratedDemo.csproj", "{DB138C8B-7C34-4AC7-A443-A0B29D1CE8A5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B452269D-856C-4FE6-8900-3D81461AF864}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B452269D-856C-4FE6-8900-3D81461AF864}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B452269D-856C-4FE6-8900-3D81461AF864}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B452269D-856C-4FE6-8900-3D81461AF864}.Release|Any CPU.Build.0 = Release|Any CPU + {DB138C8B-7C34-4AC7-A443-A0B29D1CE8A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB138C8B-7C34-4AC7-A443-A0B29D1CE8A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB138C8B-7C34-4AC7-A443-A0B29D1CE8A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB138C8B-7C34-4AC7-A443-A0B29D1CE8A5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {623C84B6-B8A4-4F29-8E68-4ED37D4529D5} + EndGlobalSection +EndGlobal diff --git a/samples/CSharp/TreeTransforms/TransformVisitor.cs b/samples/CSharp/TreeTransforms/TransformVisitor.cs new file mode 100644 index 000000000..ad969ed26 --- /dev/null +++ b/samples/CSharp/TreeTransforms/TransformVisitor.cs @@ -0,0 +1,460 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace TreeTransforms +{ + public class TransformVisitor : CSharpSyntaxRewriter + { + private readonly SyntaxTree tree; + private readonly TransformKind transformKind; + + public TransformVisitor(SyntaxTree tree, TransformKind transKind) + { + this.tree = tree; + transformKind = transKind; + } + + public override SyntaxNode VisitAnonymousMethodExpression(AnonymousMethodExpressionSyntax node) + { + node = (AnonymousMethodExpressionSyntax)base.VisitAnonymousMethodExpression(node); + + if (transformKind == TransformKind.AnonMethodToLambda) + { + SyntaxToken arrowToken = SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken); + + return SyntaxFactory.ParenthesizedLambdaExpression(default(SyntaxToken), node.ParameterList, arrowToken, node.Block); + } + + return node; + } + + public override SyntaxNode VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node) + { + node = (ParenthesizedLambdaExpressionSyntax)base.VisitParenthesizedLambdaExpression(node); + + if (transformKind == TransformKind.LambdaToAnonMethod) + { + // If any of the lambda parameters do not have type explicitly specified then we don't do any transforms. + foreach (ParameterSyntax parameter in node.ParameterList.Parameters) + { + if (parameter.Type == null) + { + return node; + } + } + + // If the body of the lambda is not a block syntax we don't do any transforms. + if (node.Body.Kind() != SyntaxKind.Block) + { + return node; + } + + return SyntaxFactory.AnonymousMethodExpression( + default(SyntaxToken), + SyntaxFactory.Token(SyntaxKind.DelegateKeyword), + node.ParameterList, + (BlockSyntax)node.Body); + } + + return node; + } + + public override SyntaxNode VisitDoStatement(DoStatementSyntax node) + { + node = (DoStatementSyntax)base.VisitDoStatement(node); + + if (transformKind == TransformKind.DoToWhile) + { + // Get the different syntax nodes components of the Do Statement + SyntaxToken doKeyword = node.DoKeyword; + StatementSyntax doStatement = node.Statement; + SyntaxToken whileKeyword = node.WhileKeyword; + ExpressionSyntax condition = node.Condition; + SyntaxToken openParen = node.OpenParenToken; + SyntaxToken closeParen = node.CloseParenToken; + SyntaxToken semicolon = node.SemicolonToken; + + // Preserve some level of trivia that was in the original Do keyword node. + SyntaxToken newWhileKeyword = SyntaxFactory.Token(doKeyword.LeadingTrivia, SyntaxKind.WhileKeyword, whileKeyword.TrailingTrivia); + + // Preserve some level of trivia that was in the original Do keyword node and the original CloseParen token. + List newCloseParenTrivias = closeParen.TrailingTrivia.ToList(); + newCloseParenTrivias.AddRange(doKeyword.TrailingTrivia.ToList()); + SyntaxTriviaList newCloseParenTriviaList = SyntaxFactory.TriviaList(newCloseParenTrivias); + SyntaxToken newCloseParen = SyntaxFactory.Token(closeParen.LeadingTrivia, SyntaxKind.CloseParenToken, newCloseParenTriviaList); + + List newTrailingTrivias = doStatement.GetTrailingTrivia().ToList(); + newTrailingTrivias.AddRange(semicolon.TrailingTrivia.ToList()); + StatementSyntax newWhileStatement = doStatement.WithTrailingTrivia(newTrailingTrivias); + + return SyntaxFactory.WhileStatement(newWhileKeyword, openParen, condition, newCloseParen, newWhileStatement); + } + + return node; + } + + public override SyntaxNode VisitWhileStatement(WhileStatementSyntax node) + { + node = (WhileStatementSyntax)base.VisitWhileStatement(node); + + if (transformKind == TransformKind.WhileToDo) + { + // Get the different syntax nodes components of the While Statement + SyntaxToken whileKeyword = node.WhileKeyword; + SyntaxToken openParen = node.OpenParenToken; + ExpressionSyntax condition = node.Condition; + SyntaxToken closeParen = node.CloseParenToken; + StatementSyntax whileStatement = node.Statement; + + // Preserve as much trivia and formatting info as possible while constructing the new nodes. + SyntaxToken newDoKeyword = SyntaxFactory.Token(whileKeyword.LeadingTrivia, SyntaxKind.DoKeyword, closeParen.TrailingTrivia); + SyntaxToken newWhileKeyword = SyntaxFactory.Token(SyntaxFactory.TriviaList(SyntaxFactory.ElasticMarker), SyntaxKind.WhileKeyword, whileKeyword.TrailingTrivia); + SyntaxToken semiColonToken = SyntaxFactory.Token(SyntaxFactory.TriviaList(SyntaxFactory.ElasticMarker), SyntaxKind.SemicolonToken, whileStatement.GetTrailingTrivia()); + SyntaxToken newCloseParen = SyntaxFactory.Token(closeParen.LeadingTrivia, SyntaxKind.CloseParenToken, SyntaxFactory.TriviaList(SyntaxFactory.ElasticMarker)); + StatementSyntax newDoStatement = whileStatement.ReplaceTrivia(whileStatement.GetTrailingTrivia().Last(), SyntaxFactory.TriviaList()); + + return SyntaxFactory.DoStatement(newDoKeyword, newDoStatement, newWhileKeyword, openParen, condition, newCloseParen, semiColonToken); + } + + return node; + } + + public override SyntaxNode VisitCheckedStatement(CheckedStatementSyntax node) + { + node = (CheckedStatementSyntax)base.VisitCheckedStatement(node); + + // Get the components of the checked statement + SyntaxToken keyword = node.Keyword; + BlockSyntax block = node.Block; + + if ((transformKind == TransformKind.CheckedStmtToUncheckedStmt) && (keyword.Kind() == SyntaxKind.CheckedKeyword)) + { + SyntaxToken uncheckedToken = SyntaxFactory.Token(keyword.LeadingTrivia, SyntaxKind.UncheckedKeyword, keyword.TrailingTrivia); + + return SyntaxFactory.CheckedStatement(SyntaxKind.UncheckedStatement, uncheckedToken, block); + } + + if ((transformKind == TransformKind.UncheckedStmtToCheckedStmt) && (keyword.Kind() == SyntaxKind.UncheckedKeyword)) + { + SyntaxToken checkedToken = SyntaxFactory.Token(keyword.LeadingTrivia, SyntaxKind.CheckedKeyword, keyword.TrailingTrivia); + return SyntaxFactory.CheckedStatement(SyntaxKind.CheckedStatement, checkedToken, block); + } + + return node; + } + + public override SyntaxNode VisitCheckedExpression(CheckedExpressionSyntax node) + { + node = (CheckedExpressionSyntax)base.VisitCheckedExpression(node); + + // Get the components of the checked expression + SyntaxToken keyword = node.Keyword; + SyntaxToken openParenToken = SyntaxFactory.Token(node.OpenParenToken.LeadingTrivia, SyntaxKind.OpenParenToken, node.OpenParenToken.TrailingTrivia); + ExpressionSyntax expression = node.Expression; + SyntaxToken closeParenToken = SyntaxFactory.Token(node.CloseParenToken.LeadingTrivia, SyntaxKind.CloseParenToken, node.CloseParenToken.TrailingTrivia); + + if ((transformKind == TransformKind.CheckedExprToUncheckedExpr) && (keyword.Kind() == SyntaxKind.CheckedKeyword)) + { + SyntaxToken uncheckedToken = SyntaxFactory.Token(keyword.LeadingTrivia, SyntaxKind.UncheckedKeyword, keyword.TrailingTrivia); + + return SyntaxFactory.CheckedExpression(SyntaxKind.UncheckedExpression, uncheckedToken, openParenToken, expression, closeParenToken); + } + + if ((transformKind == TransformKind.UncheckedExprToCheckedExpr) && (keyword.Kind() == SyntaxKind.UncheckedKeyword)) + { + SyntaxToken checkedToken = SyntaxFactory.Token(keyword.LeadingTrivia, SyntaxKind.CheckedKeyword, keyword.TrailingTrivia); + + return SyntaxFactory.CheckedExpression(SyntaxKind.CheckedExpression, checkedToken, openParenToken, expression, closeParenToken); + } + + return node; + } + + public override SyntaxNode VisitLiteralExpression(LiteralExpressionSyntax node) + { + node = (LiteralExpressionSyntax)base.VisitLiteralExpression(node); + + SyntaxToken token = node.Token; + + if ((transformKind == TransformKind.TrueToFalse) && (node.Kind() == SyntaxKind.TrueLiteralExpression)) + { + SyntaxToken newToken = SyntaxFactory.Token(token.LeadingTrivia, SyntaxKind.FalseKeyword, token.TrailingTrivia); + + return SyntaxFactory.LiteralExpression(SyntaxKind.FalseLiteralExpression, newToken); + } + + if ((transformKind == TransformKind.FalseToTrue) && (node.Kind() == SyntaxKind.FalseLiteralExpression)) + { + SyntaxToken newToken = SyntaxFactory.Token(token.LeadingTrivia, SyntaxKind.TrueKeyword, token.TrailingTrivia); + + return SyntaxFactory.LiteralExpression(SyntaxKind.TrueLiteralExpression, newToken); + } + + return node; + } + + public override SyntaxNode VisitAssignmentExpression(AssignmentExpressionSyntax node) + { + node = (AssignmentExpressionSyntax)base.VisitAssignmentExpression(node); + ExpressionSyntax left = node.Left; + ExpressionSyntax right = node.Right; + SyntaxToken operatorToken = node.OperatorToken; + + if ((transformKind == TransformKind.AddAssignToAssign) && (node.Kind() == SyntaxKind.AddAssignmentExpression)) + { + SyntaxToken equalsToken = SyntaxFactory.Token(operatorToken.LeadingTrivia, SyntaxKind.EqualsToken, operatorToken.TrailingTrivia); + ExpressionSyntax newLeft = left.WithLeadingTrivia(SyntaxFactory.TriviaList()); + BinaryExpressionSyntax addExpression = SyntaxFactory.BinaryExpression(SyntaxKind.AddExpression, newLeft, SyntaxFactory.Token(operatorToken.LeadingTrivia, SyntaxKind.PlusToken, operatorToken.TrailingTrivia), right); + + return SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, left, equalsToken, addExpression); + } + + return node; + } + + public override SyntaxNode VisitParameter(ParameterSyntax node) + { + node = (ParameterSyntax)base.VisitParameter(node); + + if ((transformKind == TransformKind.RefParamToOutParam) || (transformKind == TransformKind.OutParamToRefParam)) + { + List listOfModifiers = new List(); + + foreach (SyntaxToken modifier in node.Modifiers) + { + SyntaxToken modifierToken = modifier; + + if ((modifier.Kind() == SyntaxKind.RefKeyword) && (transformKind == TransformKind.RefParamToOutParam)) + { + modifierToken = SyntaxFactory.Token(modifierToken.LeadingTrivia, SyntaxKind.OutKeyword, modifierToken.TrailingTrivia); + } + else if ((modifier.Kind() == SyntaxKind.OutKeyword) && (transformKind == TransformKind.OutParamToRefParam)) + { + modifierToken = SyntaxFactory.Token(modifierToken.LeadingTrivia, SyntaxKind.RefKeyword, modifierToken.TrailingTrivia); + } + + listOfModifiers.Add(modifierToken); + } + + SyntaxTokenList newModifiers = SyntaxFactory.TokenList(listOfModifiers); + + return SyntaxFactory.Parameter(node.AttributeLists, newModifiers, node.Type, node.Identifier, node.Default); + } + + return node; + } + + public override SyntaxNode VisitArgument(ArgumentSyntax node) + { + node = (ArgumentSyntax)base.VisitArgument(node); + + SyntaxToken refOrOut = node.RefOrOutKeyword; + + if ((transformKind == TransformKind.RefArgToOutArg) && (refOrOut.Kind() == SyntaxKind.RefKeyword)) + { + SyntaxToken outKeyword = SyntaxFactory.Token(refOrOut.LeadingTrivia, SyntaxKind.OutKeyword, refOrOut.TrailingTrivia); + + return SyntaxFactory.Argument(node.NameColon, outKeyword, node.Expression); + } + + if ((transformKind == TransformKind.OutArgToRefArg) && (refOrOut.Kind() == SyntaxKind.OutKeyword)) + { + SyntaxToken refKeyword = SyntaxFactory.Token(refOrOut.LeadingTrivia, SyntaxKind.RefKeyword, refOrOut.TrailingTrivia); + + return SyntaxFactory.Argument(node.NameColon, refKeyword, node.Expression); + } + + return node; + } + + public override SyntaxNode VisitOrdering(OrderingSyntax node) + { + node = (OrderingSyntax)base.VisitOrdering(node); + + SyntaxToken orderingKind = node.AscendingOrDescendingKeyword; + + if ((transformKind == TransformKind.OrderByAscToOrderByDesc) && (orderingKind.Kind() == SyntaxKind.AscendingKeyword)) + { + SyntaxToken descToken = SyntaxFactory.Token(orderingKind.LeadingTrivia, SyntaxKind.DescendingKeyword, orderingKind.TrailingTrivia); + + return SyntaxFactory.Ordering(SyntaxKind.DescendingOrdering, node.Expression, descToken); + } + + if ((transformKind == TransformKind.OrderByDescToOrderByAsc) && (orderingKind.Kind() == SyntaxKind.DescendingKeyword)) + { + SyntaxToken ascToken = SyntaxFactory.Token(orderingKind.LeadingTrivia, SyntaxKind.AscendingKeyword, orderingKind.TrailingTrivia); + + return SyntaxFactory.Ordering(SyntaxKind.AscendingOrdering, node.Expression, ascToken); + } + + return node; + } + + public override SyntaxNode VisitVariableDeclaration(VariableDeclarationSyntax node) + { + node = (VariableDeclarationSyntax)base.VisitVariableDeclaration(node); + + TypeSyntax type = node.Type; + SeparatedSyntaxList declarations = node.Variables; + + List listOfVariables = new List(); + + List listOfSeperators = new List(); + + if (transformKind == TransformKind.DefaultInitAllVars) + { + foreach (VariableDeclaratorSyntax decl in declarations) + { + if (decl.Initializer == null) + { + TypeSyntax newType = type; + + if (newType.HasLeadingTrivia) + { + newType = newType.WithLeadingTrivia(new SyntaxTriviaList()); + } + + if (newType.HasTrailingTrivia) + { + newType = newType.WithLeadingTrivia(new SyntaxTriviaList()); + } + + SyntaxTrivia whiteSpaceTrivia = SyntaxFactory.Whitespace(" "); + DefaultExpressionSyntax defaultExpr = SyntaxFactory.DefaultExpression(newType); + EqualsValueClauseSyntax equalsClause = SyntaxFactory.EqualsValueClause(SyntaxFactory.Token(SyntaxFactory.TriviaList(whiteSpaceTrivia), SyntaxKind.EqualsToken, SyntaxFactory.TriviaList(whiteSpaceTrivia)), defaultExpr); + + VariableDeclaratorSyntax newDecl = SyntaxFactory.VariableDeclarator(decl.Identifier, decl.ArgumentList, equalsClause); + listOfVariables.Add(newDecl); + } + else + { + listOfVariables.Add(decl); + } + } + + for (int i = 0; i < declarations.SeparatorCount; i++) + { + SyntaxToken seperator = declarations.GetSeparator(i); + listOfSeperators.Add(SyntaxFactory.Token(seperator.LeadingTrivia, seperator.Kind(), seperator.TrailingTrivia)); + } + + SeparatedSyntaxList seperatedSyntaxList = SyntaxFactory.SeparatedList(listOfVariables, listOfSeperators); + + return SyntaxFactory.VariableDeclaration(type, seperatedSyntaxList); + } + + return node; + } + + public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) + { + node = (ClassDeclarationSyntax)base.VisitClassDeclaration(node); + SyntaxToken typeDeclKindKeyword = node.Keyword; + + if (transformKind == TransformKind.ClassDeclToStructDecl) + { + SyntaxToken structToken = SyntaxFactory.Token(typeDeclKindKeyword.LeadingTrivia, SyntaxKind.StructKeyword, typeDeclKindKeyword.TrailingTrivia); + + return SyntaxFactory.StructDeclaration(node.AttributeLists, node.Modifiers, structToken, node.Identifier, + node.TypeParameterList, node.BaseList, node.ConstraintClauses, node.OpenBraceToken, node.Members, node.CloseBraceToken, + node.SemicolonToken); + } + + return node; + } + + public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node) + { + node = (StructDeclarationSyntax)base.VisitStructDeclaration(node); + SyntaxToken typeDeclKindKeyword = node.Keyword; + + if (transformKind == TransformKind.StructDeclToClassDecl) + { + SyntaxToken classToken = SyntaxFactory.Token(typeDeclKindKeyword.LeadingTrivia, SyntaxKind.ClassKeyword, typeDeclKindKeyword.TrailingTrivia); + + return SyntaxFactory.ClassDeclaration(node.AttributeLists, node.Modifiers, classToken, node.Identifier, + node.TypeParameterList, node.BaseList, node.ConstraintClauses, node.OpenBraceToken, node.Members, node.CloseBraceToken, + node.SemicolonToken); + } + + return node; + } + + public override SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax node) + { + return base.VisitInterfaceDeclaration(node); + } + + public override SyntaxNode VisitPredefinedType(PredefinedTypeSyntax node) + { + node = (PredefinedTypeSyntax)base.VisitPredefinedType(node); + SyntaxToken token = node.Keyword; + + if ((transformKind == TransformKind.IntTypeToLongType) && (token.Kind() == SyntaxKind.IntKeyword)) + { + SyntaxToken longToken = SyntaxFactory.Token(token.LeadingTrivia, SyntaxKind.LongKeyword, token.TrailingTrivia); + + return SyntaxFactory.PredefinedType(longToken); + } + + return node; + } + + public override SyntaxNode VisitPostfixUnaryExpression(PostfixUnaryExpressionSyntax node) + { + node = (PostfixUnaryExpressionSyntax)base.VisitPostfixUnaryExpression(node); + + if (transformKind == TransformKind.PostfixToPrefix) + { + SyntaxToken operatorToken = node.OperatorToken; + ExpressionSyntax operand = node.Operand; + + SyntaxToken newOperatorToken = SyntaxFactory.Token(operand.GetLeadingTrivia(), operatorToken.Kind(), SyntaxFactory.TriviaList(SyntaxFactory.ElasticMarker)); + ExpressionSyntax newOperand = operand.WithLeadingTrivia(operatorToken.LeadingTrivia); + newOperand = newOperand.WithTrailingTrivia(operatorToken.TrailingTrivia); + + if (node.Kind() == SyntaxKind.PostIncrementExpression) + { + return SyntaxFactory.PrefixUnaryExpression(SyntaxKind.PreIncrementExpression, newOperatorToken, newOperand); + } + + if (node.Kind() == SyntaxKind.PostDecrementExpression) + { + return SyntaxFactory.PrefixUnaryExpression(SyntaxKind.PreDecrementExpression, newOperatorToken, newOperand); + } + } + + return node; + } + + public override SyntaxNode VisitPrefixUnaryExpression(PrefixUnaryExpressionSyntax node) + { + node = (PrefixUnaryExpressionSyntax)base.VisitPrefixUnaryExpression(node); + + if (transformKind == TransformKind.PrefixToPostfix) + { + SyntaxToken operatorToken = node.OperatorToken; + ExpressionSyntax operand = node.Operand; + + SyntaxToken newOperatorToken = SyntaxFactory.Token(SyntaxFactory.TriviaList(SyntaxFactory.ElasticMarker), operatorToken.Kind(), operand.GetTrailingTrivia()); + ExpressionSyntax newOperand = operand.WithTrailingTrivia(operatorToken.TrailingTrivia); + newOperand = newOperand.WithLeadingTrivia(operatorToken.LeadingTrivia); + + if (node.Kind() == SyntaxKind.PreIncrementExpression) + { + return SyntaxFactory.PostfixUnaryExpression(SyntaxKind.PostIncrementExpression, newOperand, newOperatorToken); + } + + if (node.Kind() == SyntaxKind.PreDecrementExpression) + { + return SyntaxFactory.PostfixUnaryExpression(SyntaxKind.PostDecrementExpression, newOperand, newOperatorToken); + } + } + + return node; + } + } +} diff --git a/samples/CSharp/TreeTransforms/Transforms.cs b/samples/CSharp/TreeTransforms/Transforms.cs new file mode 100644 index 000000000..1901a9fb4 --- /dev/null +++ b/samples/CSharp/TreeTransforms/Transforms.cs @@ -0,0 +1,53 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace TreeTransforms +{ + /// + /// Kinds of Syntax transforms. + /// + public enum TransformKind + { + LambdaToAnonMethod, + AnonMethodToLambda, + DoToWhile, + WhileToDo, + CheckedStmtToUncheckedStmt, + UncheckedStmtToCheckedStmt, + CheckedExprToUncheckedExpr, + UncheckedExprToCheckedExpr, + PostfixToPrefix, + PrefixToPostfix, + TrueToFalse, + FalseToTrue, + AddAssignToAssign, + RefParamToOutParam, + OutParamToRefParam, + RefArgToOutArg, + OutArgToRefArg, + OrderByAscToOrderByDesc, + OrderByDescToOrderByAsc, + DefaultInitAllVars, + ClassDeclToStructDecl, + StructDeclToClassDecl, + IntTypeToLongType, + } + + public class Transforms + { + /// + /// Performs a syntax transform of the source code which is passed in as a string. The transform to be performed is also passed as an argument + /// + /// Text of the source code which is to be transformed + /// The kind of Syntax Transform that needs to be performed on the source + /// Transformed source code as a string + public static string Transform(string sourceText, TransformKind transformKind) + { + SyntaxTree sourceTree = SyntaxFactory.ParseSyntaxTree(sourceText); + TransformVisitor visitor = new TransformVisitor(sourceTree, transformKind); + + return visitor.Visit(sourceTree.GetRoot()).ToFullString(); + } + } +} diff --git a/samples/CSharp/TreeTransforms/TreeTransformTests.cs b/samples/CSharp/TreeTransforms/TreeTransformTests.cs new file mode 100644 index 000000000..775a0f227 --- /dev/null +++ b/samples/CSharp/TreeTransforms/TreeTransformTests.cs @@ -0,0 +1,759 @@ +using Xunit; + +namespace TreeTransforms +{ + public static class TreeTransformTests + { + [Fact] + public static void LambdaToAnonMethodTest() + { + string input = @" +public class Test +{ + public static void Main(string[] args) + { + Func f1 = (int x, int y) => { return x + y; }; + } +}"; + + string expected_transform = @" +public class Test +{ + public static void Main(string[] args) + { + Func f1 = delegate(int x, int y) { return x + y; }; + } +}"; + + string actual_transform = Transforms.Transform(input, TransformKind.LambdaToAnonMethod); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void AnonMethodToLambdaTest() + { + string input = @" +public class Test +{ + public static void Main(string[] args) + { + Func f1 = delegate(int x, int y) { return x + y; }; + } +}"; + + string expected_transform = @" +public class Test +{ + public static void Main(string[] args) + { + Func f1 = (int x, int y) =>{ return x + y; }; + } +}"; + string actual_transform = Transforms.Transform(input, TransformKind.AnonMethodToLambda); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void DoToWhileTest() + { + string input = @" +class Program +{ + static void Main() + { + int i = 0; + int sum = 0; + do + { + sum += i; + i++; + } while (i < 10); + System.Console.WriteLine(sum); + } +} +"; + + string expected_transform = @" +class Program +{ + static void Main() + { + int i = 0; + int sum = 0; + while (i < 10) + { + sum += i; + i++; + } + System.Console.WriteLine(sum); + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.DoToWhile); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void WhileToDoTest() + { + string input = @" +class Program +{ + static void Main() + { + int i = 0; + int sum = 0; + while (i < 10) + { + sum += i; + i++; + } + System.Console.WriteLine(sum); + } +} +"; + + string expected_transform = @" +class Program +{ + static void Main() + { + int i = 0; + int sum = 0; + do + { + sum += i; + i++; + }while (i < 10); + System.Console.WriteLine(sum); + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.WhileToDo); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void CheckedStmtToUncheckedStmtTest() + { + string input = @" +class Program +{ + static void Main() + { + checked + { + int x = int.MaxValue; + x = x + 1; + } + } +} +"; + + string expected_transform = @" +class Program +{ + static void Main() + { + unchecked + { + int x = int.MaxValue; + x = x + 1; + } + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.CheckedStmtToUncheckedStmt); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void UncheckedStmtToCheckedStmt() + { + string input = @" +class Program +{ + static void Main() + { + unchecked + { + int x = int.MaxValue; + x = x + 1; + } + } +} +"; + + string expected_transform = @" +class Program +{ + static void Main() + { + checked + { + int x = int.MaxValue; + x = x + 1; + } + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.UncheckedStmtToCheckedStmt); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void CheckedExprToUncheckedExprTest() + { + string input = @" +class Program +{ + static void Main() + { + int x = int.MaxValue; + x = checked(x + 1); + } +} +"; + + string expected_transform = @" +class Program +{ + static void Main() + { + int x = int.MaxValue; + x = unchecked(x + 1); + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.CheckedExprToUncheckedExpr); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void UncheckedExprToCheckedExprTest() + { + string input = @" +class Program +{ + static void Main() + { + int x = int.MaxValue; + x = unchecked(x + 1); + } +} +"; + + string expected_transform = @" +class Program +{ + static void Main() + { + int x = int.MaxValue; + x = checked(x + 1); + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.UncheckedExprToCheckedExpr); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void PostfixToPrefixTest() + { + string input = @" +class Program +{ + static void Main() + { + int x = 10; + /*START*/ x++ /*END*/; + x--; + } +} +"; + + string expected_transform = @" +class Program +{ + static void Main() + { + int x = 10; + /*START*/ ++x /*END*/; + --x; + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.PostfixToPrefix); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void PrefixToPostfixTest() + { + string input = @" +class Program +{ + static void Main() + { + int x = 10; + /*START*/ ++x /*END*/; + --x; + } +} +"; + + string expected_transform = @" +class Program +{ + static void Main() + { + int x = 10; + /*START*/ x++ /*END*/; + x--; + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.PrefixToPostfix); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void TrueToFalseTest() + { + string input = @" +class Program +{ + static void Main() + { + bool b1 = true; + if (true) + { + } + } +} +"; + + string expected_transform = @" +class Program +{ + static void Main() + { + bool b1 = false; + if (false) + { + } + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.TrueToFalse); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void FalseToTrueTest() + { + string input = @" +class Program +{ + static void Main() + { + bool b1 = false; + if (false) + { + } + } +} +"; + + string expected_transform = @" +class Program +{ + static void Main() + { + bool b1 = true; + if (true) + { + } + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.FalseToTrue); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void AddAssignToAssignTest() + { + string input = @" +class Program +{ + static void Main() + { + int x = 10; + int y = 45; + x += y; + } +} +"; + + string expected_transform = @" +class Program +{ + static void Main() + { + int x = 10; + int y = 45; + x = x + y; + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.AddAssignToAssign); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void RefParamToOutParamTest() + { + string input = @" +class Program +{ + static void Method1(ref int i1, out int i2, int i3) + { + i2 = 45; + } + static void Main() + { + int x = 4, y = 5, z = 6; + Method1(ref x, out y, z); + } +} +"; + + string expected_transform = @" +class Program +{ + static void Method1(out int i1, out int i2, int i3) + { + i2 = 45; + } + static void Main() + { + int x = 4, y = 5, z = 6; + Method1(ref x, out y, z); + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.RefParamToOutParam); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void OutParamToRefParamTest() + { + string input = @" +class Program +{ + static void Method1(ref int i1, out int i2, int i3) + { + i2 = 45; + } + static void Main() + { + int x = 4, y = 5, z = 6; + Method1(ref x, out y, z); + } +} +"; + + string expected_transform = @" +class Program +{ + static void Method1(ref int i1, ref int i2, int i3) + { + i2 = 45; + } + static void Main() + { + int x = 4, y = 5, z = 6; + Method1(ref x, out y, z); + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.OutParamToRefParam); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void RefArgToOutArgTest() + { + string input = @" +class Program +{ + static void Method1(ref int i1, out int i2, int i3) + { + i2 = 45; + } + static void Main() + { + int x = 4, y = 5, z = 6; + Method1(ref x, out y, z); + } +} +"; + + string expected_transform = @" +class Program +{ + static void Method1(ref int i1, out int i2, int i3) + { + i2 = 45; + } + static void Main() + { + int x = 4, y = 5, z = 6; + Method1(out x, out y, z); + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.RefArgToOutArg); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void OutArgToRefArgTest() + { + string input = @" +class Program +{ + static void Method1(ref int i1, out int i2, int i3) + { + i2 = 45; + } + static void Main() + { + int x = 4, y = 5, z = 6; + Method1(ref x, out y, z); + } +} +"; + + string expected_transform = @" +class Program +{ + static void Method1(ref int i1, out int i2, int i3) + { + i2 = 45; + } + static void Main() + { + int x = 4, y = 5, z = 6; + Method1(ref x, ref y, z); + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.OutArgToRefArg); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void OrderByAscToOrderByDescTest() + { + string input = @" +using System; +using System.Linq; +class Program +{ + static void Main() + { + int[] numbers = { 3, 1, 4, 6, 10 }; + var sortedNumbers = from number in numbers orderby number ascending select number; + foreach (var num in sortedNumbers) + Console.WriteLine(num); + } +} +"; + + string expected_transform = @" +using System; +using System.Linq; +class Program +{ + static void Main() + { + int[] numbers = { 3, 1, 4, 6, 10 }; + var sortedNumbers = from number in numbers orderby number descending select number; + foreach (var num in sortedNumbers) + Console.WriteLine(num); + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.OrderByAscToOrderByDesc); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void OrderByDescToOrderByAscTest() + { + string input = @" +using System; +using System.Linq; +class Program +{ + static void Main() + { + int[] numbers = { 3, 1, 4, 6, 10 }; + var sortedNumbers = from number in numbers orderby number descending select number; + foreach (var num in sortedNumbers) + Console.WriteLine(num); + } +} +"; + + string expected_transform = @" +using System; +using System.Linq; +class Program +{ + static void Main() + { + int[] numbers = { 3, 1, 4, 6, 10 }; + var sortedNumbers = from number in numbers orderby number ascending select number; + foreach (var num in sortedNumbers) + Console.WriteLine(num); + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.OrderByDescToOrderByAsc); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void DefaultInitAllVarsTest() + { + string input = @" +class Program +{ + static void Main() + { + int i, j; + Program f1; + } +} +"; + + string expected_transform = @" +class Program +{ + static void Main() + { + int i = default(int ), j = default(int ); + Program f1 = default(Program ); + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.DefaultInitAllVars); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void ClassDeclToStructDeclTest() + { + string input = @" +class Program +{ + static void Main() + { + } +} +"; + + string expected_transform = @" +struct Program +{ + static void Main() + { + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.ClassDeclToStructDecl); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void StructDeclToClassDeclTest() + { + string input = @" +struct Program +{ + static void Main() + { + } +} +"; + + string expected_transform = @" +class Program +{ + static void Main() + { + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.StructDeclToClassDecl); + + Assert.Equal(expected_transform, actual_transform); + } + + [Fact] + public static void IntTypeToLongTypeTest() + { + string input = @" +using System.Collections.Generic; +class Program +{ + static void Main() + { + int i; + List l1 = new List(); + } +} +"; + + string expected_transform = @" +using System.Collections.Generic; +class Program +{ + static void Main() + { + long i; + List l1 = new List(); + } +} +"; + string actual_transform = Transforms.Transform(input, TransformKind.IntTypeToLongType); + + Assert.Equal(expected_transform, actual_transform); + } + } +} diff --git a/samples/CSharp/TreeTransforms/TreeTransforms.CSharp.UnitTests.csproj b/samples/CSharp/TreeTransforms/TreeTransforms.CSharp.UnitTests.csproj new file mode 100644 index 000000000..089cdb050 --- /dev/null +++ b/samples/CSharp/TreeTransforms/TreeTransforms.CSharp.UnitTests.csproj @@ -0,0 +1,11 @@ + + + + net472 + + + + + + + diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props new file mode 100644 index 000000000..4ed5c9361 --- /dev/null +++ b/samples/Directory.Build.props @@ -0,0 +1,16 @@ + + + + false + false + + + NU1701;$(NoWarn) + + + false + + + + + \ No newline at end of file diff --git a/samples/Directory.Build.targets b/samples/Directory.Build.targets new file mode 100644 index 000000000..c19f17c24 --- /dev/null +++ b/samples/Directory.Build.targets @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/samples/Shared/UnitTestFramework/CodeActionProviderTestFixture.cs b/samples/Shared/UnitTestFramework/CodeActionProviderTestFixture.cs new file mode 100644 index 000000000..e2a95a0a5 --- /dev/null +++ b/samples/Shared/UnitTestFramework/CodeActionProviderTestFixture.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Text; +using Xunit; + +namespace Roslyn.UnitTestFramework +{ + public abstract class CodeActionProviderTestFixture + { + protected Document CreateDocument(string code) + { + string fileExtension = LanguageName == LanguageNames.CSharp ? ".cs" : ".vb"; + + ProjectId projectId = ProjectId.CreateNewId(debugName: "TestProject"); + DocumentId documentId = DocumentId.CreateNewId(projectId, debugName: "Test" + fileExtension); + + // find these assemblies in the running process + string[] simpleNames = { "mscorlib", "System.Core", "System" }; + + IEnumerable references = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => simpleNames.Contains(a.GetName().Name, StringComparer.OrdinalIgnoreCase)) + .Select(a => MetadataReference.CreateFromFile(a.Location)); + + return new AdhocWorkspace().CurrentSolution + .AddProject(projectId, "TestProject", "TestProject", LanguageName) + .AddMetadataReferences(projectId, references) + .AddDocument(documentId, "Test" + fileExtension, SourceText.From(code)) + .GetDocument(documentId); + } + + protected void VerifyDocument(string expected, bool compareTokens, Document document) + { + if (compareTokens) + { + VerifyTokens(expected, Format(document).ToString()); + } + else + { + VerifyText(expected, document); + } + } + + private SyntaxNode Format(Document document) + { + Document updatedDocument = document.WithSyntaxRoot(document.GetSyntaxRootAsync().Result); + return Formatter.FormatAsync(Simplifier.ReduceAsync(updatedDocument, Simplifier.Annotation).Result, Formatter.Annotation).Result.GetSyntaxRootAsync().Result; + } + + private IList ParseTokens(string text) + { + return LanguageName == LanguageNames.CSharp + ? Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParseTokens(text).Select(t => (SyntaxToken)t).ToList() + : Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory.ParseTokens(text).Select(t => (SyntaxToken)t).ToList(); + } + + private bool VerifyTokens(string expected, string actual) + { + IList expectedNewTokens = ParseTokens(expected); + IList actualNewTokens = ParseTokens(actual); + + for (int i = 0; i < Math.Min(expectedNewTokens.Count, actualNewTokens.Count); i++) + { + Assert.Equal(expectedNewTokens[i].ToString(), actualNewTokens[i].ToString()); + } + + if (expectedNewTokens.Count != actualNewTokens.Count) + { + string expectedDisplay = string.Join(" ", expectedNewTokens.Select(t => t.ToString())); + string actualDisplay = string.Join(" ", actualNewTokens.Select(t => t.ToString())); + Assert.True(false, + string.Format("Wrong token count. Expected '{0}', Actual '{1}', Expected Text: '{2}', Actual Text: '{3}'", + expectedNewTokens.Count, actualNewTokens.Count, expectedDisplay, actualDisplay)); + } + + return true; + } + + private bool VerifyText(string expected, Document document) + { + string actual = Format(document).ToString(); + Assert.Equal(expected, actual); + return true; + } + + protected abstract string LanguageName { get; } + } +} diff --git a/samples/Shared/UnitTestFramework/CodeRefactoringProviderTestFixture.cs b/samples/Shared/UnitTestFramework/CodeRefactoringProviderTestFixture.cs new file mode 100644 index 000000000..69951f75c --- /dev/null +++ b/samples/Shared/UnitTestFramework/CodeRefactoringProviderTestFixture.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Text; +using Xunit; + +namespace Roslyn.UnitTestFramework +{ + public abstract class CodeRefactoringProviderTestFixture : CodeActionProviderTestFixture + { + private IEnumerable GetRefactoring(Document document, TextSpan span) + { + CodeRefactoringProvider provider = CreateCodeRefactoringProvider; + List actions = new List(); + CodeRefactoringContext context = new CodeRefactoringContext(document, span, (a) => actions.Add(a), CancellationToken.None); + provider.ComputeRefactoringsAsync(context).Wait(); + return actions; + } + + protected void TestNoActions(string markup) + { + if (!markup.Contains('\r')) + { + markup = markup.Replace("\n", "\r\n"); + } + + MarkupTestFile.GetSpan(markup, out string code, out TextSpan span); + + Document document = CreateDocument(code); + IEnumerable actions = GetRefactoring(document, span); + + Assert.True(actions == null || actions.Count() == 0); + } + + protected void Test( + string markup, + string expected, + int actionIndex = 0, + bool compareTokens = false) + { + if (!markup.Contains('\r')) + { + markup = markup.Replace("\n", "\r\n"); + } + + if (!expected.Contains('\r')) + { + expected = expected.Replace("\n", "\r\n"); + } + + MarkupTestFile.GetSpan(markup, out string code, out TextSpan span); + + Document document = CreateDocument(code); + IEnumerable actions = GetRefactoring(document, span); + + Assert.NotNull(actions); + + CodeAction action = actions.ElementAt(actionIndex); + Assert.NotNull(action); + + ApplyChangesOperation edit = action.GetOperationsAsync(CancellationToken.None).Result.OfType().First(); + VerifyDocument(expected, compareTokens, edit.ChangedSolution.GetDocument(document.Id)); + } + + protected abstract CodeRefactoringProvider CreateCodeRefactoringProvider { get; } + } +} diff --git a/samples/Shared/UnitTestFramework/DictionaryExtensions.cs b/samples/Shared/UnitTestFramework/DictionaryExtensions.cs new file mode 100644 index 000000000..7d08828f0 --- /dev/null +++ b/samples/Shared/UnitTestFramework/DictionaryExtensions.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Roslyn.UnitTestFramework +{ + internal static class DictionaryExtensions + { + // Copied from ConcurrentDictionary since IDictionary doesn't have this useful method + public static TValue GetOrAdd(this IDictionary dictionary, TKey key, Func function) + { + if (!dictionary.TryGetValue(key, out TValue value)) + { + value = function(key); + dictionary.Add(key, value); + } + + return value; + } + + public static TValue GetOrAdd(this IDictionary dictionary, TKey key, Func function) + => dictionary.GetOrAdd(key, _ => function()); + } +} diff --git a/samples/Shared/UnitTestFramework/EnumerableExtensions.ComparisonComparer.cs b/samples/Shared/UnitTestFramework/EnumerableExtensions.ComparisonComparer.cs new file mode 100644 index 000000000..f24fe16e3 --- /dev/null +++ b/samples/Shared/UnitTestFramework/EnumerableExtensions.ComparisonComparer.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Roslyn.UnitTestFramework +{ + internal static partial class EnumerableExtensions + { + private class ComparisonComparer : Comparer + { + private readonly Comparison _compare; + + public ComparisonComparer(Comparison compare) + { + _compare = compare; + } + + public override int Compare(T x, T y) + { + return _compare(x, y); + } + } + } +} diff --git a/samples/Shared/UnitTestFramework/EnumerableExtensions.cs b/samples/Shared/UnitTestFramework/EnumerableExtensions.cs new file mode 100644 index 000000000..bf84d9a27 --- /dev/null +++ b/samples/Shared/UnitTestFramework/EnumerableExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Roslyn.UnitTestFramework +{ + internal static partial class EnumerableExtensions + { + public static IEnumerable OrderBy(this IEnumerable source, IComparer comparer) + { + return source.OrderBy(t => t, comparer); + } + + public static IEnumerable OrderBy(this IEnumerable source, Comparison compare) + { + return source.OrderBy(new ComparisonComparer(compare)); + } + } +} diff --git a/samples/Shared/UnitTestFramework/MarkupTestFile.cs b/samples/Shared/UnitTestFramework/MarkupTestFile.cs new file mode 100644 index 000000000..376a21f64 --- /dev/null +++ b/samples/Shared/UnitTestFramework/MarkupTestFile.cs @@ -0,0 +1,332 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace Roslyn.UnitTestFramework +{ + /// + /// To aid with testing, we define a special type of text file that can encode additional + /// information in it. This prevents a test writer from having to carry around multiple sources + /// of information that must be reconstituted. For example, instead of having to keep around the + /// contents of a file *and* and the location of the cursor, the tester can just provide a + /// string with the "$" character in it. This allows for easy creation of "FIT" tests where all + /// that needs to be provided are strings that encode every bit of state necessary in the string + /// itself. + /// + /// The current set of encoded features we support are: + /// + /// $$ - The position in the file. There can be at most one of these. + /// + /// [| ... |] - A span of text in the file. There can be many of these and they can be nested + /// and/or overlap the $ position. + /// + /// {|Name: ... |} A span of text in the file annotated with an identifier. There can be many of + /// these, including ones with the same name. + /// + /// Additional encoded features can be added on a case by case basis. + /// + public static class MarkupTestFile + { + private const string PositionString = "$$"; + private const string SpanStartString = "[|"; + private const string SpanEndString = "|]"; + private const string NamedSpanStartString = "{|"; + private const string NamedSpanEndString = "|}"; + + private static readonly Regex s_namedSpanStartRegex = new Regex(@"\{\| ([^:]+) \:", + RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace); + + private static void Parse(string input, out string output, out int? position, out IDictionary> spans) + { + position = null; + spans = new Dictionary>(); + + StringBuilder outputBuilder = new StringBuilder(); + + int currentIndexInInput = 0; + int inputOutputOffset = 0; + + // A stack of span starts along with their associated annotation name. [||] spans simply + // have empty string for their annotation name. + Stack> spanStartStack = new Stack>(); + + while (true) + { + List> matches = new List>(); + AddMatch(input, PositionString, currentIndexInInput, matches); + AddMatch(input, SpanStartString, currentIndexInInput, matches); + AddMatch(input, SpanEndString, currentIndexInInput, matches); + AddMatch(input, NamedSpanEndString, currentIndexInInput, matches); + + Match namedSpanStartMatch = s_namedSpanStartRegex.Match(input, currentIndexInInput); + if (namedSpanStartMatch.Success) + { + matches.Add(Tuple.Create(namedSpanStartMatch.Index, namedSpanStartMatch.Value)); + } + + if (matches.Count == 0) + { + // No more markup to process. + break; + } + + List> orderedMatches = matches.OrderBy((t1, t2) => t1.Item1 - t2.Item1).ToList(); + if (orderedMatches.Count >= 2 && + spanStartStack.Count > 0 && + matches[0].Item1 == matches[1].Item1 - 1) + { + // We have a slight ambiguity with cases like these: + // + // [|] [|} + // + // Is it starting a new match, or ending an existing match. As a workaround, we + // special case these and consider it ending a match if we have something on the + // stack already. + if ((matches[0].Item2 == SpanStartString && matches[1].Item2 == SpanEndString && spanStartStack.Peek().Item2 == string.Empty) || + (matches[0].Item2 == SpanStartString && matches[1].Item2 == NamedSpanEndString && spanStartStack.Peek().Item2 != string.Empty)) + { + orderedMatches.RemoveAt(0); + } + } + + // Order the matches by their index + Tuple firstMatch = orderedMatches.First(); + + int matchIndexInInput = firstMatch.Item1; + string matchString = firstMatch.Item2; + + int matchIndexInOutput = matchIndexInInput - inputOutputOffset; + outputBuilder.Append(input.Substring(currentIndexInInput, matchIndexInInput - currentIndexInInput)); + + currentIndexInInput = matchIndexInInput + matchString.Length; + inputOutputOffset += matchString.Length; + + switch (matchString.Substring(0, 2)) + { + case PositionString: + if (position.HasValue) + { + throw new ArgumentException(string.Format("Saw multiple occurrences of {0}", PositionString)); + } + + position = matchIndexInOutput; + break; + + case SpanStartString: + spanStartStack.Push(Tuple.Create(matchIndexInOutput, string.Empty)); + break; + + case SpanEndString: + if (spanStartStack.Count == 0) + { + throw new ArgumentException(string.Format("Saw {0} without matching {1}", SpanEndString, SpanStartString)); + } + + if (spanStartStack.Peek().Item2.Length > 0) + { + throw new ArgumentException(string.Format("Saw {0} without matching {1}", NamedSpanStartString, NamedSpanEndString)); + } + + PopSpan(spanStartStack, spans, matchIndexInOutput); + break; + + case NamedSpanStartString: + string name = namedSpanStartMatch.Groups[1].Value; + spanStartStack.Push(Tuple.Create(matchIndexInOutput, name)); + break; + + case NamedSpanEndString: + if (spanStartStack.Count == 0) + { + throw new ArgumentException(string.Format("Saw {0} without matching {1}", NamedSpanEndString, NamedSpanStartString)); + } + + if (spanStartStack.Peek().Item2.Length == 0) + { + throw new ArgumentException(string.Format("Saw {0} without matching {1}", SpanStartString, SpanEndString)); + } + + PopSpan(spanStartStack, spans, matchIndexInOutput); + break; + + default: + throw new InvalidOperationException(); + } + } + + if (spanStartStack.Count > 0) + { + throw new ArgumentException(string.Format("Saw {0} without matching {1}", SpanStartString, SpanEndString)); + } + + // Append the remainder of the string. + outputBuilder.Append(input.Substring(currentIndexInInput)); + output = outputBuilder.ToString(); + } + + private static void PopSpan( + Stack> spanStartStack, + IDictionary> spans, + int finalIndex) + { + Tuple spanStartTuple = spanStartStack.Pop(); + + TextSpan span = TextSpan.FromBounds(spanStartTuple.Item1, finalIndex); + spans.GetOrAdd(spanStartTuple.Item2, () => new List()).Add(span); + } + + private static void AddMatch(string input, string value, int currentIndex, List> matches) + { + int index = input.IndexOf(value, currentIndex); + if (index >= 0) + { + matches.Add(Tuple.Create(index, value)); + } + } + + public static void GetPositionAndSpans(string input, out string output, out int? cursorPositionOpt, out IDictionary> spans) + { + Parse(input, out output, out cursorPositionOpt, out spans); + } + + public static void GetPositionAndSpans(string input, out int? cursorPositionOpt, out IDictionary> spans) + { + GetPositionAndSpans(input, out string output, out cursorPositionOpt, out spans); + } + + public static void GetPositionAndSpans(string input, out string output, out int cursorPosition, out IDictionary> spans) + { + GetPositionAndSpans(input, out output, out int? cursorPositionOpt, out spans); + + cursorPosition = cursorPositionOpt.Value; + } + + public static void GetSpans(string input, out string output, out IDictionary> spans) + { + GetPositionAndSpans(input, out output, out int? cursorPositionOpt, out spans); + } + + public static void GetPositionAndSpans(string input, out string output, out int? cursorPositionOpt, out IList spans) + { + Parse(input, out output, out cursorPositionOpt, out IDictionary> dictionary); + + spans = dictionary.GetOrAdd(string.Empty, () => new List()); + } + + public static void GetPositionAndSpans(string input, out int? cursorPositionOpt, out IList spans) + { + GetPositionAndSpans(input, out string output, out cursorPositionOpt, out spans); + } + + public static void GetPositionAndSpans(string input, out string output, out int cursorPosition, out IList spans) + { + GetPositionAndSpans(input, out output, out int? pos, out spans); + + cursorPosition = pos ?? 0; + } + + public static void GetPosition(string input, out string output, out int cursorPosition) + { + GetPositionAndSpans(input, out output, out cursorPosition, out IList spans); + } + + public static void GetPositionAndSpan(string input, out string output, out int cursorPosition, out TextSpan span) + { + GetPositionAndSpans(input, out output, out cursorPosition, out IList spans); + + span = spans.Single(); + } + + public static void GetSpans(string input, out string output, out IList spans) + { + GetPositionAndSpans(input, out output, out int? pos, out spans); + } + + public static void GetSpan(string input, out string output, out TextSpan span) + { + GetSpans(input, out output, out IList spans); + + span = spans.Single(); + } + + public static string CreateTestFile(string code, int cursor) + { + return CreateTestFile(code, (IDictionary>)null, cursor); + } + + public static string CreateTestFile(string code, IList spans, int cursor = -1) + { + return CreateTestFile(code, new Dictionary> { { string.Empty, spans } }, cursor); + } + + public static string CreateTestFile(string code, IDictionary> spans, int cursor = -1) + { + StringBuilder sb = new StringBuilder(); + IList anonymousSpans = spans.GetOrAdd(string.Empty, () => new List()); + + for (int i = 0; i <= code.Length; i++) + { + if (i == cursor) + { + sb.Append(PositionString); + } + + AddSpanString(sb, spans.Where(kvp => kvp.Key != string.Empty), i, start: true); + AddSpanString(sb, spans.Where(kvp => kvp.Key == string.Empty), i, start: true); + AddSpanString(sb, spans.Where(kvp => kvp.Key == string.Empty), i, start: false); + AddSpanString(sb, spans.Where(kvp => kvp.Key != string.Empty), i, start: false); + + if (i < code.Length) + { + sb.Append(code[i]); + } + } + + return sb.ToString(); + } + + private static void AddSpanString( + StringBuilder sb, + IEnumerable>> items, + int position, + bool start) + { + foreach (KeyValuePair> kvp in items) + { + foreach (TextSpan span in kvp.Value) + { + if (start && span.Start == position) + { + if (kvp.Key == string.Empty) + { + sb.Append(SpanStartString); + } + else + { + sb.Append(NamedSpanStartString); + sb.Append(kvp.Key); + sb.Append(':'); + } + } + else if (!start && span.End == position) + { + if (kvp.Key == string.Empty) + { + sb.Append(SpanEndString); + } + else + { + sb.Append(NamedSpanEndString); + } + } + } + } + } + } +} diff --git a/samples/Shared/UnitTestFramework/Roslyn.UnitTestFramework.csproj b/samples/Shared/UnitTestFramework/Roslyn.UnitTestFramework.csproj new file mode 100644 index 000000000..d37b89d6b --- /dev/null +++ b/samples/Shared/UnitTestFramework/Roslyn.UnitTestFramework.csproj @@ -0,0 +1,13 @@ + + + + netcoreapp2.0 + + + + + + + + + diff --git a/samples/VisualBasic/APISamples/APISamples.VisualBasic.UnitTests.vbproj b/samples/VisualBasic/APISamples/APISamples.VisualBasic.UnitTests.vbproj new file mode 100644 index 000000000..1b5bb2c92 --- /dev/null +++ b/samples/VisualBasic/APISamples/APISamples.VisualBasic.UnitTests.vbproj @@ -0,0 +1,11 @@ + + + + netcoreapp2.0 + + + + + + + diff --git a/samples/VisualBasic/APISamples/Compilations.vb b/samples/VisualBasic/APISamples/Compilations.vb new file mode 100644 index 000000000..f9f1f0628 --- /dev/null +++ b/samples/VisualBasic/APISamples/Compilations.vb @@ -0,0 +1,79 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Globalization +Imports System.IO +Imports System.Reflection +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Xunit + +Public Class Compilations + + + Sub EndToEndCompileAndRun() + Dim expression = "6 * 7" + Dim code = + +Public Module Calculator + Public Function Evaluate() As Object + Return $ + End Function +End Module +.GetCode().Replace("$", expression) + + Dim tree = SyntaxFactory.ParseSyntaxTree(code) + Dim comp = VisualBasicCompilation.Create( + "calc.dll", + options:=New VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithEmbedVbCoreRuntime(True), + syntaxTrees:={tree}, + references:={MetadataReference.CreateFromFile(GetType(Object).Assembly.Location), + MetadataReference.CreateFromFile(GetType(CompilerServices.StandardModuleAttribute).Assembly.Location)}) + + Dim compiledAssembly As Assembly + Using stream = New MemoryStream() + Dim compileResult = comp.Emit(stream) + Assert.True(compileResult.Success) + compiledAssembly = Assembly.Load(stream.GetBuffer()) + End Using + + Dim calculator = compiledAssembly.GetType("Calculator") + Dim evaluate = calculator.GetMethod("Evaluate") + Dim answer = evaluate.Invoke(Nothing, Nothing).ToString() + Assert.Equal("42", answer) + End Sub + + + Sub GetErrorsAndWarnings() + Dim code = + +Module Module1 + Function Main() As Integer + End Function +End Module +.GetCode() + + Dim tree = SyntaxFactory.ParseSyntaxTree(code) + Dim comp = VisualBasicCompilation.Create( + "program.exe", + syntaxTrees:={tree}, + references:={MetadataReference.CreateFromFile(GetType(Object).Assembly.Location), + MetadataReference.CreateFromFile(GetType(CompilerServices.StandardModuleAttribute).Assembly.Location)}) + comp = comp.WithOptions(comp.Options.WithEmbedVbCoreRuntime(True)) + + Dim errorsAndWarnings = comp.GetDiagnostics() + Assert.Equal(1, errorsAndWarnings.Count()) + + Dim err As Diagnostic = errorsAndWarnings.First() + Assert.Equal("Function 'Main' doesn't return a value on all code paths. Are you missing a 'Return' statement?", err.GetMessage(CultureInfo.InvariantCulture)) + + Dim errorLocation = err.Location + Assert.Equal(12, errorLocation.SourceSpan.Length) + + Dim programText = errorLocation.SourceTree.GetText() + Assert.Equal("End Function", programText.ToString(errorLocation.SourceSpan)) + + Dim span = err.Location.GetLineSpan() + Assert.Equal(4, span.StartLinePosition.Character) + Assert.Equal(2, span.StartLinePosition.Line) + End Sub +End Class diff --git a/samples/VisualBasic/APISamples/Extensions.vb b/samples/VisualBasic/APISamples/Extensions.vb new file mode 100644 index 000000000..23905caed --- /dev/null +++ b/samples/VisualBasic/APISamples/Extensions.vb @@ -0,0 +1,22 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Runtime.CompilerServices + +Module Extensions + + + Function GetCode(xml As XElement) As String + Dim code = xml.Value + + If code.First() = vbLf Then + code = code.Remove(0, 1) + End If + + If code.Last() = vbLf Then + code = code.Remove(code.Length - 1) + End If + + Return code.Replace(vbLf, vbCrLf) + End Function + +End Module diff --git a/samples/VisualBasic/APISamples/FAQ.vb b/samples/VisualBasic/APISamples/FAQ.vb new file mode 100644 index 000000000..22108272a --- /dev/null +++ b/samples/VisualBasic/APISamples/FAQ.vb @@ -0,0 +1,2444 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.IO +Imports System.Text +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.FindSymbols +Imports Microsoft.CodeAnalysis.Formatting +Imports Microsoft.CodeAnalysis.Simplification +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Xunit + +Namespace APISampleUnitTestsVB + + Public Class FAQ + + + Private Class FAQAttribute + Inherits Attribute + + Private ReadOnly _Id As Integer + + Public ReadOnly Property Id As Integer + Get + Return _Id + End Get + End Property + + Public Sub New(id As Integer) + _Id = id + End Sub + End Class + + + Private _Mscorlib As MetadataReference + + Public ReadOnly Property Mscorlib As MetadataReference + Get + If _Mscorlib Is Nothing Then + _Mscorlib = MetadataReference.CreateFromFile(GetType(Object).Assembly.Location) + End If + Return _Mscorlib + End Get + End Property + +#Region " Section 1 : Getting Information Questions " + + + + Public Sub GetTypeForTypeName() + + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Module Program + + Public Sub Main() + + Dim i As Integer = 0 + i += 1 + + End Sub +End Module +.Value) + Dim vbRuntime = MetadataReference.CreateFromFile(GetType(CompilerServices.StandardModuleAttribute).Assembly.Location) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib, vbRuntime}) + Dim model = comp.GetSemanticModel(tree) + + ' Get TypeSyntax corresponding to the keyword 'Integer' above. + Dim typeName = + Aggregate + node In tree.GetRoot().DescendantNodes.OfType(Of TypeSyntax)() + Where + node.ToString() = "Integer" + Into [Single]() + + ' Use GetTypeInfo() to get TypeSymbol corresponding to the keyword 'Integer' above. + Dim type = CType(model.GetTypeInfo(typeName).Type, ITypeSymbol) + + Assert.Equal(SpecialType.System_Int32, type.SpecialType) + Assert.Equal("Integer", type.ToDisplayString()) + + ' Alternately, use GetSymbolInfo() to get TypeSymbol corresponding to keyword 'Integer' above. + type = CType(model.GetSymbolInfo(typeName).Symbol, ITypeSymbol) + + Assert.Equal(SpecialType.System_Int32, type.SpecialType) + Assert.Equal("Integer", type.ToDisplayString()) + End Sub + + + + Public Sub GetTypeForVariableDeclaration() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Module Program + + Public Sub Main() + + Dim i = 0 : i += 1 + End Sub +End Module +.Value) + + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib}, options:=vbOptions) + + Dim model = comp.GetSemanticModel(tree) + + ' Get ModifiedIdentifierSyntax corresponding to the identifier 'i' in the statement 'Dim i = ...' above. + Dim identifier As ModifiedIdentifierSyntax = tree.GetRoot() _ + .DescendantNodes _ + .OfType(Of VariableDeclaratorSyntax) _ + .Single _ + .Names _ + .Single + + ' Get TypeSymbol corresponding to 'Dim i' above. + Dim type = CType(model.GetDeclaredSymbol(identifier), ILocalSymbol).Type + + Assert.Equal(SpecialType.System_Int32, type.SpecialType) + Assert.Equal("Integer", type.ToDisplayString()) + End Sub + + + + Public Sub GetTypeForExpressions() + Dim source = + +Imports System + +Module Program + + Public Sub M(s As Short()) + Dim d = 1.0 + Console.WriteLine(s(0) + d) + End Sub + + Public Sub Main() + End Sub +End Module +.Value + + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + + Dim _projectId = ProjectId.CreateNewId() + Dim _documentId = DocumentId.CreateNewId(_projectId) + + Dim sln = New AdhocWorkspace().CurrentSolution. + AddProject(_projectId, "MyProject", "MyProject", LanguageNames.VisualBasic).WithProjectCompilationOptions(_projectId, vbOptions). + AddMetadataReference(_projectId, Mscorlib). + AddDocument(_documentId, "MyFile.vb", source) + Dim document = sln.GetDocument(_documentId) + Dim model = CType(document.GetSemanticModelAsync().Result, SemanticModel) + + ' Get BinaryExpressionSyntax corresponding to the expression 's(0) + d' above. + Dim addExpression As BinaryExpressionSyntax = document.GetSyntaxRootAsync().Result. + DescendantNodes. + OfType(Of BinaryExpressionSyntax). + Single + + ' Get TypeSymbol corresponding to expression 's(0) + d' above. + Dim expressionTypeInfo As TypeInfo = model.GetTypeInfo(addExpression) + Dim expressionType = expressionTypeInfo.Type + + Assert.Equal(SpecialType.System_Double, expressionType.SpecialType) + Assert.Equal("Double", expressionType.ToDisplayString()) + Assert.Equal(SpecialType.System_Double, expressionTypeInfo.ConvertedType.SpecialType) + Assert.True(model.GetConversion(addExpression).IsIdentity) + + ' Get IdentifierNameSyntax corresponding to the variable 'd' in expression 's(0) + d' above. + Dim identifier = CType(addExpression.Right, IdentifierNameSyntax) + + ' Use GetTypeInfo() to get TypeSymbol corresponding to variable 'd' above. + Dim variableTypeInfo As TypeInfo = model.GetTypeInfo(identifier) + Dim variableType = variableTypeInfo.Type + + Assert.Equal(SpecialType.System_Double, variableType.SpecialType) + Assert.Equal("Double", variableType.ToDisplayString()) + Assert.Equal(SpecialType.System_Double, variableTypeInfo.ConvertedType.SpecialType) + Assert.True(model.GetConversion(identifier).IsIdentity) + + ' Alternately, use GetSymbolInfo() to get TypeSymbol corresponding to variable 'd' above. + variableType = (CType(model.GetSymbolInfo(identifier).Symbol, ILocalSymbol)).Type + + Assert.Equal(SpecialType.System_Double, variableType.SpecialType) + Assert.Equal("Double", variableType.ToDisplayString()) + + ' Get InvocationExpressionSyntax corresponding to 's(0)' in expression 's(0) + d' above. + Dim elementAccess = CType(addExpression.Left, InvocationExpressionSyntax) + + ' Use GetTypeInfo() to get TypeSymbol corresponding to 's(0)' above. + expressionTypeInfo = model.GetTypeInfo(elementAccess) + expressionType = expressionTypeInfo.Type + + Assert.Equal(SpecialType.System_Int16, expressionType.SpecialType) + Assert.Equal("Short", expressionType.ToDisplayString()) + Assert.Equal(SpecialType.System_Double, expressionTypeInfo.ConvertedType.SpecialType) + + Dim conv = model.GetConversion(elementAccess) + Assert.True(conv.IsWidening AndAlso conv.IsNumeric) + + ' Get IdentifierNameSyntax corresponding to the parameter 's' in expression 's(0) + d' above. + identifier = CType(elementAccess.Expression, IdentifierNameSyntax) + + ' Use GetTypeInfo() to get TypeSymbol corresponding to parameter 's' above. + variableTypeInfo = model.GetTypeInfo(identifier) + variableType = variableTypeInfo.Type + + Assert.Equal("Short()", variableType.ToDisplayString()) + Assert.Equal("Short()", variableTypeInfo.ConvertedType.ToDisplayString()) + Assert.True(model.GetConversion(identifier).IsIdentity) + + ' Alternately, use GetSymbolInfo() to get TypeSymbol corresponding to parameter 's' above. + variableType = (CType(model.GetSymbolInfo(identifier).Symbol, IParameterSymbol)).Type + + Assert.Equal("Short()", variableType.ToDisplayString()) + Assert.Equal(SpecialType.System_Int16, CType(variableType, IArrayTypeSymbol).ElementType.SpecialType) + End Sub + + + + Public Sub GetInScopeSymbols() + Dim source = + +Class C + +End Class + +Module Program + + Private i As Integer = 0 + + Public Sub Main() + + Dim j As Integer = 0 : j += i + + ' What symbols are in scope here? + End Sub +End Module +.Value + + Dim tree = SyntaxFactory.ParseSyntaxTree(source) + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib}, options:=vbOptions) + Dim model = comp.GetSemanticModel(tree) + + ' Get position of the comment above. + Dim position = source.IndexOf("' ") + + ' Get 'all' symbols that are in scope at the above position. + Dim symbols = model.LookupSymbols(position) + + ' Note: "Windows" only appears as a symbol at this location in Windows 8.1. + Dim actual = String.Join(vbLf, From symbol In symbols + Select result = symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat) + Where result <> "Windows" + Order By result) + + Dim expected As String = C +FxResources +Internal +j As Integer +Microsoft +Program +Program.i As Integer +Sub Program.Main() +System.Value + Assert.Equal(expected, actual) + + ' Filter results by looking at Kind of returned symbols (only get locals and fields). + actual = String.Join(vbLf, From symbol In symbols + Where symbol.Kind = SymbolKind.Local OrElse + symbol.Kind = SymbolKind.Field + Select result = symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat) + Order By result) + Assert.Equal( +j As Integer +Program.i As Integer.Value, actual) + + ' Filter results - get namespaces and types. + ' Note: "Windows" only appears as a symbol at this location in Windows 8.1. + symbols = model.LookupNamespacesAndTypes(position) + actual = String.Join(vbLf, From symbol In symbols + Select result = symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat) + Where result <> "Windows" + Order By result) + + Assert.Equal( +C +FxResources +Internal +Microsoft +Program +System.Value, actual) + End Sub + + + + Public Sub GetSymbolsForAccessibleMembersOfAType() + Dim source = + +Imports System + +Public Class C + + Friend InstanceField As Integer = 0 + + Public Property InstanceProperty As Integer + + Friend Sub InstanceMethod() + Console.WriteLine(InstanceField) + End Sub + + Protected Sub InaccessibleInstanceMethod() + Console.WriteLine(InstanceProperty) + End Sub +End Class + +Public Module ExtensionMethods + <System.Runtime.CompilerServices.Extension> + Public Sub ExtensionMethod(s As C) + End Sub +End Module + +Module Program + + Sub Main() + Dim c As C = New C() + c.ToString() + End Sub +End Module +.Value + + Dim tree = SyntaxFactory.ParseSyntaxTree(source) + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib}, options:=vbOptions) + + Dim model = comp.GetSemanticModel(tree) + + ' Get position of 'c.ToString()' above. + Dim position = source.IndexOf("c.ToString()") + + ' Get IdentifierNameSyntax corresponding to identifier 'c' above. + Dim identifier = CType(tree.GetRoot().FindToken(position).Parent, IdentifierNameSyntax) + + ' Get TypeSymbol corresponding to variable 'c' above. + Dim type = model.GetTypeInfo(identifier).Type + + ' Get symbols for 'accessible' members on the above TypeSymbol. + Dim symbols = model.LookupSymbols(position, container:=type, includeReducedExtensionMethods:=True) + + Dim results = String.Join(vbLf, From symbol In symbols + Select result = symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat) + Order By result) + Assert.Equal( +C.InstanceField As Integer +Function Object.Equals(obj As Object) As Boolean +Function Object.Equals(objA As Object, objB As Object) As Boolean +Function Object.GetHashCode() As Integer +Function Object.GetType() As Type +Function Object.ReferenceEquals(objA As Object, objB As Object) As Boolean +Function Object.ToString() As String +Property C.InstanceProperty As Integer +Sub C.ExtensionMethod() +Sub C.InstanceMethod().Value, results) + End Sub + + + + Public Sub FindAllInvocationsOfAMethod() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Class C1 + + Public Sub M1() + M2() + End Sub + + Public Sub M2() + End Sub +End Class + +Class C2 + + Public Sub M1() + M2() + Call New C1().M2() + End Sub + + Public Sub M2() + End Sub +End Class + +Module Program + + Sub Main() + End Sub +End Module +.Value) + + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib}, options:=vbOptions) + Dim model = comp.GetSemanticModel(tree) + + ' Get MethodBlockSyntax corresponding to method C1.M2() above. + Dim methodDeclaration As MethodBlockSyntax = + Aggregate c In tree.GetRoot().DescendantNodes.OfType(Of ClassBlockSyntax)() + Where c.ClassStatement.Identifier.ValueText = "C1" + From m In c.Members.OfType(Of MethodBlockSyntax)() + Where CType(m.SubOrFunctionStatement, MethodStatementSyntax).Identifier.ValueText = "M2" + Select m + Into [Single]() + + ' Get MethodSymbol corresponding to method C1.M2() above. + Dim method = CType(model.GetDeclaredSymbol(methodDeclaration), IMethodSymbol) + + ' Get all InvocationExpressionSyntax in the above code. + Dim allInvocations = tree.GetRoot().DescendantNodes.OfType(Of InvocationExpressionSyntax)() + + ' Use GetSymbolInfo() to find invocations of method C1.M2() above. + Dim matchingInvocations = From i In allInvocations Where model.GetSymbolInfo(i).Symbol.Equals(method) + + Assert.Equal(2, matchingInvocations.Count) + End Sub + + + + Public Sub FindAllReferencesToAMethodInASolution() + Dim source1 = +Namespace NS + + Public Class C + + Public Sub MethodThatWeAreTryingToFind() + End Sub + + Public Sub AnotherMethod() + MethodThatWeAreTryingToFind() ' First Reference. + End Sub + End Class +End Namespace.Value + Dim source2 = +Imports NS +Imports AliasedType = NS.C + +Module Program + + Sub Main() + Dim c1 = New C() + c1.MethodThatWeAreTryingToFind() ' Second Reference. + c1.AnotherMethod() + Dim c2 = New AliasedType() + c2.MethodThatWeAreTryingToFind() ' Third Reference. + End Sub +End Module.Value + Dim _project1Id = ProjectId.CreateNewId(), + _project2Id = ProjectId.CreateNewId() + Dim _document1Id = DocumentId.CreateNewId(_project1Id), + _document2Id = DocumentId.CreateNewId(_project2Id) + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + + Dim sln = New AdhocWorkspace().CurrentSolution. + AddProject(_project1Id, "Project1", "Project1", LanguageNames.VisualBasic). + AddMetadataReference(_project1Id, Mscorlib). + AddDocument(_document1Id, "File1.vb", source1). + WithProjectCompilationOptions(_project1Id, New VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithEmbedVbCoreRuntime(True)). + AddProject(_project2Id, "Project2", "Project2", LanguageNames.VisualBasic).WithProjectCompilationOptions(_project2Id, vbOptions). + AddMetadataReference(_project2Id, Mscorlib). + AddProjectReference(_project2Id, New ProjectReference(_project1Id)). + AddDocument(_document2Id, "File2.vb", source2) + + ' If you wish to try against a real solution you could use code like + ' Dim sln = Solution.Load("") + ' OR Dim sln = Workspace.LoadSolution("").CurrentSolution + Dim project1 = sln.GetProject(_project1Id) + Dim document1 = project1.GetDocument(_document1Id) + + ' Get MethodBlockSyntax corresponding to the 'MethodThatWeAreTryingToFind'. + Dim methodBlock As MethodBlockSyntax = document1.GetSyntaxRootAsync().Result.DescendantNodes. + OfType(Of MethodBlockSyntax). + Single(Function(m) m.SubOrFunctionStatement.Identifier.ValueText = "MethodThatWeAreTryingToFind") + + ' Get MethodSymbol corresponding to the 'MethodThatWeAreTryingToFind'. + Dim method = document1.GetSemanticModelAsync().Result.GetDeclaredSymbol(methodBlock) + + ' Find all references to the 'MethodThatWeAreTryingToFind' in the solution. + Dim methodReferences = SymbolFinder.FindReferencesAsync(method, sln).Result + + Assert.Equal(1, methodReferences.Count) + + Dim methodReference = methodReferences.Single() + + Assert.Equal(3, methodReference.Locations.Count) + + Dim methodDefinition = CType(methodReference.Definition, IMethodSymbol) + + Assert.Equal("MethodThatWeAreTryingToFind", methodDefinition.Name) + Assert.True(methodReference.Definition.Locations.Single.IsInSource) + Assert.Equal("File1.vb", methodReference.Definition.Locations.Single.SourceTree.FilePath) + Assert.True(methodReference.Locations.All(Function(referenceLocation) referenceLocation.Location.IsInSource)) + Assert.Equal(1, methodReference.Locations.Count(Function(referenceLocation) referenceLocation.Document.Name = "File1.vb")) + Assert.Equal(2, methodReference.Locations.Count(Function(referenceLocation) referenceLocation.Document.Name = "File2.vb")) + End Sub + + + + Public Sub FindAllInvocationsToMethodsFromAParticularNamespace() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Imports System +Imports System.Threading.Tasks + +Module Program + + Sub Main() + Dim a As Action = Sub() Return + Dim t = Task.Factory.StartNew(a) + t.Wait() + Console.WriteLine(a.ToString()) + + a = Sub() + t = New Task(a) + t.Start() + t.Wait() + End Sub + a() + End Sub +End Module +.Value) + + Dim consoleReference = MetadataReference.CreateFromFile(GetType(Console).Assembly.Location) + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib, consoleReference}, options:=vbOptions) + + Dim model = comp.GetSemanticModel(tree) + + ' Instantiate MethodInvocationWalker (below) and tell it to find invocations to methods from the System.Threading.Tasks namespace. + Dim walker = New MethodInvocationWalker With {.SemanticModel = model, .NamespaceName = "System.Threading.Tasks"} + walker.Visit(tree.GetRoot()) + + Assert.Equal( + +Line 8: Task.Factory.StartNew(a) +Line 9: t.Wait() +Line 13: New Task(a) +Line 14: t.Start() +Line 15: t.Wait().Value, walker.Results.ToString()) + End Sub + + ' Below SyntaxWalker checks all nodes of type ObjectCreationExpressionSyntax or InvocationExpressionSyntax + ' present under the SyntaxNode being visited to detect invocations to methods from the supplied namespace. + Public Class MethodInvocationWalker + Inherits VisualBasicSyntaxWalker + + Public Property SemanticModel As SemanticModel + + Public Property NamespaceName As String + + Public Property Results As New StringBuilder() + + Private Function CheckWhetherMethodIsFromNamespace(node As ExpressionSyntax) As Boolean + Dim isMatch = False + If SemanticModel IsNot Nothing Then + Dim symbolInfo = SemanticModel.GetSymbolInfo(node) + + Dim ns As String = symbolInfo.Symbol.ContainingNamespace.ToDisplayString() + If ns = NamespaceName Then + Results.Append(vbLf) + Results.Append("Line ") + Results.Append(SemanticModel.SyntaxTree.GetLineSpan(node.Span).StartLinePosition.Line) + Results.Append(": ") + Results.Append(node.ToString()) + isMatch = True + End If + End If + + Return isMatch + End Function + + Public Overrides Sub VisitObjectCreationExpression(node As ObjectCreationExpressionSyntax) + CheckWhetherMethodIsFromNamespace(node) + MyBase.VisitObjectCreationExpression(node) + End Sub + + Public Overrides Sub VisitInvocationExpression(node As InvocationExpressionSyntax) + CheckWhetherMethodIsFromNamespace(node) + MyBase.VisitInvocationExpression(node) + End Sub + End Class + + + + Public Sub GetAllFieldAndMethodSymbolsInACompilation() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Imports System + +Namespace NS1 + + Public Class C + + Dim InstanceField As Integer = 0 + + Friend Sub InstanceMethod() + Console.WriteLine(InstanceField) + End Sub + End Class +End Namespace + +Namespace NS2 + + Module ExtensionMethods + <System.Runtime.CompilerServices.Extension> + Public Sub ExtensionMethod(s As NS1.C) + End Sub + End Module +End Namespace + +Module Program + + Sub Main() + Dim c As NS1.C = New NS1.C() + c.ToString() + End Sub +End Module +.Value) + + Dim vbRuntime = MetadataReference.CreateFromFile(GetType(CompilerServices.StandardModuleAttribute).Assembly.Location) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib, vbRuntime}) + Dim results = New StringBuilder() + + ' Traverse the symbol tree to find all namespaces, types, methods and fields. + For Each ns In comp.Assembly.GlobalNamespace.GetNamespaceMembers() + results.Append(vbLf) + results.Append(ns.Kind.ToString()) + results.Append(": ") + results.Append(ns.Name) + For Each typeMember In ns.GetTypeMembers() + results.Append(vbLf) + results.Append(" ") + results.Append(typeMember.TypeKind.ToString()) + results.Append(": ") + results.Append(typeMember.Name) + For Each member In typeMember.GetMembers() + results.Append(vbLf) + results.Append(" ") + If member.Kind = SymbolKind.Field OrElse member.Kind = SymbolKind.Method Then + results.Append(member.Kind.ToString()) + results.Append(": ") + results.Append(member.Name) + End If + Next + + Next + + Next + + Assert.Equal( + +Namespace: NS1 + Class: C + Method: .ctor + Field: InstanceField + Method: InstanceMethod +Namespace: NS2 + Module: ExtensionMethods + Method: ExtensionMethod.Value, results.ToString()) + End Sub + + + + Public Sub TraverseAllExpressionsInASyntaxTreeUsingAWalker() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Imports System + +Module Program + + Sub Main() + Dim i = 0.0 + i += 1 + 2L + End Sub +End Module +.Value) + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib}, options:=vbOptions) + + Dim model = comp.GetSemanticModel(tree) + Dim walker = New ExpressionWalker() With {.SemanticModel = model} + walker.Visit(tree.GetRoot()) + Assert.Equal( + +LiteralExpressionSyntax 0.0 has type Double +IdentifierNameSyntax i has type Double +BinaryExpressionSyntax 1 + 2L has type Long +LiteralExpressionSyntax 1 has type Integer +LiteralExpressionSyntax 2L has type Long.Value, walker.Results.ToString()) + End Sub + + ' Below SyntaxWalker traverses all expressions under the SyntaxNode being visited and lists the types of these expressions. + Public Class ExpressionWalker + Inherits SyntaxWalker + Public Property SemanticModel As SemanticModel + + Public Property Results As New StringBuilder() + + Public Overrides Sub Visit(node As SyntaxNode) + If TypeOf node Is ExpressionSyntax Then + Dim type = SemanticModel.GetTypeInfo(CType(node, ExpressionSyntax)).Type + If type IsNot Nothing Then + Results.Append(vbLf) + Results.Append(node.GetType().Name) + Results.Append(" ") + Results.Append(node.ToString()) + Results.Append(" has type ") + Results.Append(type.ToDisplayString()) + End If + End If + + MyBase.Visit(node) + End Sub + End Class + + + + Public Sub CompareSyntax() + Dim source = + +Imports System + +Module Program + + Sub Main() + Dim i = 0.0 + i += 1 + 2L + End Sub +End Module +.Value + Dim tree1 = SyntaxFactory.ParseSyntaxTree(source) + Dim tree2 = SyntaxFactory.ParseSyntaxTree(source) + Dim node1 As SyntaxNode = tree1.GetRoot() + Dim node2 As SyntaxNode = tree2.GetRoot() + + ' Compare trees and nodes that are identical. + Assert.True(tree1.IsEquivalentTo(tree2)) + Assert.True(node1.IsEquivalentTo(node2)) + + ' tree3 is identical to tree1 except for a single comment. + Dim tree3 = SyntaxFactory.ParseSyntaxTree( + +Imports System + +Module Program + + ' Additional comment. + Sub Main() + Dim i = 0.0 + i += 1 + 2L + End Sub +End Module +.Value) + Dim node3 As SyntaxNode = tree3.GetRoot() + + ' Compare trees and nodes that are identical except for trivia. + Assert.True(tree1.IsEquivalentTo(tree3)) ' Trivia differences are ignored. + Assert.False(node1.IsEquivalentTo(node3)) ' Trivia differences are considered. + + ' tree4 is identical to tree1 except for method body contents. + Dim tree4 = SyntaxFactory.ParseSyntaxTree( + +Imports System + +Module Program + + Sub Main() + End Sub +End Module +.Value) + + Dim node4 As SyntaxNode = tree4.GetRoot() + + ' Compare trees and nodes that are identical at the top-level. + Assert.True(tree1.IsEquivalentTo(tree4, topLevel:=True)) ' Only top-level nodes are considered. + Assert.False(node1.IsEquivalentTo(node4)) ' Non-top-level nodes are considered. + + ' Tokens and Trivia can also be compared. + Dim token1 As SyntaxToken = node1.DescendantTokens.First + Dim token2 As SyntaxToken = node2.DescendantTokens.First + + Assert.True(token1.IsEquivalentTo(token2)) + + Dim trivia1 As SyntaxTrivia = node1.DescendantTrivia().First(Function(t) t.Kind() = SyntaxKind.WhitespaceTrivia) + Dim trivia2 As SyntaxTrivia = node2.DescendantTrivia().Last(Function(t) t.Kind() = SyntaxKind.EndOfLineTrivia) + + Assert.False(trivia1.IsEquivalentTo(trivia2)) + End Sub + + + + Public Sub TraverseAllCommentsInASyntaxTreeUsingAWalker() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Imports System + +''' <summary>First Comment</summary> +Module Program + + ' Second Comment + Sub Main() + ' Third Comment + End Sub + +End Module +.Value) + + Dim walker As New CommentWalker() + walker.Visit(tree.GetRoot()) + + Assert.Equal( + +''' <summary>First Comment</summary> (Parent Token: ModuleKeyword) (Structured) +' Second Comment (Parent Token: SubKeyword) +' Third Comment (Parent Token: EndKeyword).Value, walker.Results.ToString()) + End Sub + + ' Below SyntaxWalker traverses all comments present under the SyntaxNode being visited. + Public Class CommentWalker + Inherits VisualBasicSyntaxWalker + + Public Property Results As New StringBuilder() + + Public Sub New() + MyBase.New(SyntaxWalkerDepth.StructuredTrivia) + End Sub + + Public Overrides Sub VisitTrivia(trivia As SyntaxTrivia) + If trivia.Kind() = SyntaxKind.CommentTrivia OrElse trivia.Kind() = SyntaxKind.DocumentationCommentTrivia Then + Results.Append(vbLf) + Results.Append(trivia.ToFullString().Trim()) + Results.Append(" (Parent Token: ") + Results.Append(trivia.Token.Kind.ToString()) + Results.Append(")") + + If trivia.Kind() = SyntaxKind.DocumentationCommentTrivia Then + ' Trivia for xml documentation comments have additional 'structure' + ' available under a child DocumentationCommentSyntax. + Assert.True(trivia.HasStructure) + Dim documentationComment = CType(trivia.GetStructure(), DocumentationCommentTriviaSyntax) + Assert.True(documentationComment.ParentTrivia = trivia) + Results.Append(" (Structured)") + End If + End If + + MyBase.VisitTrivia(trivia) + End Sub + End Class + + + + Public Sub CompareSymbols() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Imports System + +Class C + +End Class + +Module Program + + Public Sub Main() + Dim c = New C() + Console.WriteLine(c.ToString()) + End Sub +End Module +.Value) + + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib}, options:=vbOptions) + Dim model = comp.GetSemanticModel(tree) + + ' Get ModifiedIdentifierSyntax corresponding to the identifier 'c' in the statement 'Dim c = ...' above. + Dim identifier As ModifiedIdentifierSyntax = tree.GetRoot(). + DescendantNodes. + OfType(Of VariableDeclaratorSyntax). + Single. + Names. + Single + + ' Get TypeSymbol corresponding to 'Dim c' above. + Dim type As ITypeSymbol = CType(model.GetDeclaredSymbol(identifier), ILocalSymbol).Type + Dim expectedType As ITypeSymbol = comp.GetTypeByMetadataName("C") + + Assert.True(type.Equals(expectedType)) + End Sub + + + + Public Sub TestWhetherANodeIsPartOfATreeOrASemanticModel() + Dim source = +Imports System + +Class C + +End Class + +Module Program + + Public Sub Main() + Dim c = New C() + Console.WriteLine(c.ToString()) + End Sub +End Module +.Value + + Dim tree = SyntaxFactory.ParseSyntaxTree(source) + Dim other = SyntaxFactory.ParseSyntaxTree(source) + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib}, options:=vbOptions) + + Dim model = comp.GetSemanticModel(tree) + Dim nodeFromTree As SyntaxNode = tree.GetRoot() + Dim tokenNotFromTree As SyntaxToken = SyntaxFactory.Token(SyntaxKind.ClassKeyword) + Dim nodeNotFromTree As SyntaxNode = other.GetRoot() + + Assert.True(nodeFromTree.SyntaxTree Is tree) + Assert.True(nodeFromTree.SyntaxTree Is model.SyntaxTree) + Assert.False(tokenNotFromTree.SyntaxTree Is tree) + Assert.False(nodeNotFromTree.SyntaxTree Is model.SyntaxTree) + Assert.True(nodeNotFromTree.SyntaxTree Is other) + End Sub + + + + Public Sub ValueVersusValueTextVersusGetTextForTokens() + Dim source = +Imports System + +Module Program + + Public Sub Main() + Dim [long] = 1L + Console.WriteLine([long]) + End Sub +End Module +.Value + Dim tree = SyntaxFactory.ParseSyntaxTree(source) + + ' Get token corresponding to identifier '[long]' above. + Dim token1 As SyntaxToken = tree.GetRoot().FindToken(source.IndexOf("[long]")) + ' Get token corresponding to literal '1L' above. + Dim token2 As SyntaxToken = tree.GetRoot().FindToken(source.IndexOf("1L")) + + Assert.Equal("String", token1.Value.GetType().Name) + Assert.Equal("long", token1.Value) + Assert.Equal("long", token1.ValueText) + Assert.Equal("[long]", token1.ToString()) + + Assert.Equal("Int64", token2.Value.GetType().Name) + Assert.Equal(1L, token2.Value) + Assert.Equal("1", token2.ValueText) + Assert.Equal("1L", token2.ToString()) + End Sub + + + + Public Sub GetLineAndColumnInfo() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Module Program + + Public Sub Main() + End Sub +End Module +.Value, path:="MyCodeFile.vb") + + ' Get MethodBlockSyntax corresponding to the method block for 'Sub Main()' above. + Dim node As MethodBlockSyntax = tree.GetRoot().DescendantNodes.OfType(Of MethodBlockSyntax).Single + + ' Use GetLocation() and GetLineSpan() to get file, line and column info for above BlockSyntax. + Dim location As Location = node.GetLocation() + Dim lineSpan As FileLinePositionSpan = location.GetLineSpan() + + Assert.True(location.IsInSource) + Assert.Equal("MyCodeFile.vb", lineSpan.Path) + Assert.Equal(3, lineSpan.StartLinePosition.Line) + Assert.Equal(4, lineSpan.StartLinePosition.Character) + + ' Alternate way to get file, line and column info from any span. + location = tree.GetLocation(node.Span) + lineSpan = location.GetLineSpan() + + Assert.Equal("MyCodeFile.vb", lineSpan.Path) + Assert.Equal(3, lineSpan.StartLinePosition.Line) + Assert.Equal(4, lineSpan.StartLinePosition.Character) + + ' Yet another way to get file, line and column info from any span. + lineSpan = tree.GetLineSpan(node.Span) + + Assert.Equal("MyCodeFile.vb", lineSpan.Path) + Assert.Equal(4, lineSpan.EndLinePosition.Line) + Assert.Equal(11, lineSpan.EndLinePosition.Character) + + ' SyntaxTokens also have GetLocation(). + ' Use GetLocation() to get the position of the 'Public' token under the above MethodBlockSyntax. + Dim token As SyntaxToken = node.DescendantTokens().First + location = token.GetLocation() + lineSpan = location.GetLineSpan() + + Assert.Equal("MyCodeFile.vb", lineSpan.Path) + Assert.Equal(3, lineSpan.StartLinePosition.Line) + Assert.Equal(4, lineSpan.StartLinePosition.Character) + + ' SyntaxTrivia also have GetLocation(). + ' Use GetLocation() to get the position of the first EndOfLineTrivia under the above SyntaxToken. + Dim trivia As SyntaxTrivia = token.LeadingTrivia.First() + location = trivia.GetLocation() + lineSpan = location.GetLineSpan() + + Assert.Equal("MyCodeFile.vb", lineSpan.Path) + Assert.Equal(2, lineSpan.StartLinePosition.Line) + Assert.Equal(0, lineSpan.StartLinePosition.Character) + End Sub + + + + Public Sub GetEmptySourceLinesFromASyntaxTree() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Module Program + + Public Shared Sub Main() + End Sub +End Module +.Value, path:="MyCodeFile.vb") + + Dim text As SourceText = tree.GetText() + + Assert.Equal(7, text.Lines.Count) + + ' Enumerate empty lines. + Dim results = String.Join(vbLf, From line In text.Lines + Where String.IsNullOrWhiteSpace(line.ToString()) + Select String.Format("Line {0} (Span {1}-{2}) is empty", line.LineNumber, line.Start, line.End)) + + Assert.Equal( +Line 0 (Span 0-0) is empty +Line 2 (Span 16-16) is empty +Line 6 (Span 69-69) is empty.Value, results) + End Sub + + + + Public Sub UseSyntaxWalker() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Module Program + + Public Sub Main() +#If True Then +#End If + Dim b = True + If b Then + End If + + If Not b Then + End If + End Sub +End Module + +Structure S + +End Structure +.Value) + Dim walker = New IfStatementIfKeywordAndTypeBlockWalker() + walker.Visit(tree.GetRoot()) + Assert.Equal( + +Visiting ModuleBlockSyntax (Kind = ModuleBlock) +Visiting SyntaxToken (Kind = IfKeyword): #If True Then +Visiting SyntaxToken (Kind = IfKeyword): #End If +Visiting IfStatementSyntax (Kind = IfStatement): If b Then +Visiting SyntaxToken (Kind = IfKeyword): If b Then +Visiting SyntaxToken (Kind = IfKeyword): End If +Visiting IfStatementSyntax (Kind = IfStatement): If Not b Then +Visiting SyntaxToken (Kind = IfKeyword): If Not b Then +Visiting SyntaxToken (Kind = IfKeyword): End If +Visiting StructureBlockSyntax (Kind = StructureBlock).Value, walker.Results.ToString()) + End Sub + + ' Below SyntaxWalker traverses all IfStatementSyntax, IfKeyworkd and TypeBlockSyntax present under the SyntaxNode being visited. + Public Class IfStatementIfKeywordAndTypeBlockWalker + Inherits VisualBasicSyntaxWalker + Public Property Results As New StringBuilder() + + ' Turn on visiting of nodes, tokens and trivia present under structured trivia. + Public Sub New() + MyBase.New(SyntaxWalkerDepth.StructuredTrivia) + End Sub + + ' If you need to visit all SyntaxNodes of a particular (derived) type that appears directly + ' in a syntax tree, you can override the Visit* method corresponding to this type. + ' For example, you can override VisitIfStatement to visit all SyntaxNodes of type IfStatementSyntax. + Public Overrides Sub VisitIfStatement(node As IfStatementSyntax) + Results.Append(vbLf) + Results.Append("Visiting ") + Results.Append(node.GetType().Name) + Results.Append(" (Kind = ") + Results.Append(node.Kind().ToString()) + Results.Append("): ") + Results.Append(node.ToString()) + MyBase.VisitIfStatement(node) + End Sub + + ' Visits all SyntaxTokens. + Public Overrides Sub VisitToken(token As SyntaxToken) + ' We only care about SyntaxTokens with Kind 'IfKeyword'. + If token.Kind() = SyntaxKind.IfKeyword Then + Results.Append(vbLf) + Results.Append("Visiting ") + Results.Append(token.GetType().Name) + Results.Append(" (Kind = ") + Results.Append(token.Kind().ToString()) + Results.Append("): ") + Results.Append(token.Parent.ToString()) + End If + + MyBase.VisitToken(token) + End Sub + + ' Visits all SyntaxNodes. + Public Overrides Sub Visit(node As SyntaxNode) + ' If you need to visit all SyntaxNodes of a particular base type that can never + ' appear directly in a syntax tree then this would be the place to check for that. + ' For example, TypeBlockSyntax is a base type for all the type declarations (like + ' ModuleBlockSyntax and StructureBlockSyntax) that can appear in a syntax tree. + If TypeOf node Is TypeBlockSyntax Then + Results.Append(vbLf) + Results.Append("Visiting ") + Results.Append(node.GetType().Name) + Results.Append(" (Kind = ") + Results.Append(node.Kind().ToString()) + Results.Append(")") + End If + + MyBase.Visit(node) + End Sub + End Class + + + + Public Sub GetFullyQualifiedName() + Dim source = + +Imports System +Imports AliasedType = NS.C(Of Integer) + +Namespace NS + + Public Class C(Of T) + + Public Structure S(Of U) + + End Structure + End Class +End Namespace + +Module Program + + Public Sub Main() + Dim s As AliasedType.S(Of Long) = New AliasedType.S(Of Long)() + Console.WriteLine(s.ToString()) + End Sub +End Module +.Value + Dim _projectId = ProjectId.CreateNewId() + Dim _documentId = DocumentId.CreateNewId(_projectId) + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + + Dim sln = New AdhocWorkspace().CurrentSolution. + AddProject(_projectId, "MyProject", "MyProject", LanguageNames.VisualBasic).WithProjectCompilationOptions(_projectId, vbOptions). + AddMetadataReference(_projectId, Mscorlib). + AddDocument(_documentId, "MyFile.vb", source) + + Dim document = sln.GetDocument(_documentId) + Dim root = document.GetSyntaxRootAsync().Result + Dim model = CType(document.GetSemanticModelAsync().Result, SemanticModel) + + ' Get StructureBlockSyntax corresponding to 'Structure S' above. + Dim structBlock As StructureBlockSyntax = root.DescendantNodes.OfType(Of StructureBlockSyntax).Single + + ' Get TypeSymbol corresponding to 'Structure S' above. + Dim structType = model.GetDeclaredSymbol(structBlock) + + ' Use ToDisplayString() to get fully qualified name. + Assert.Equal("NS.C(Of T).S(Of U)", structType.ToDisplayString()) + Assert.Equal("Global.NS.C(Of T).S(Of U)", structType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)) + + ' Get ModifiedIdentifierSyntax corresponding to identifier 's' in 'Dim s As AliasedType.S(Of Long) = ...' above. + Dim modifiedIdentifier As ModifiedIdentifierSyntax = root.DescendantNodes. + OfType(Of VariableDeclaratorSyntax). + Single. + Names. + Single + + ' Get TypeSymbol corresponding to above ModifiedIdentifierSyntax. + Dim variableType = (CType(model.GetDeclaredSymbol(modifiedIdentifier), ILocalSymbol)).Type + + Assert.NotEqual(variableType, structType) ' Type of variable is a closed generic type while that of the struct is an open generic type. + Assert.Equal(Of ITypeSymbol)(variableType.OriginalDefinition, structType) ' OriginalDefinition for a closed generic type points to corresponding open generic type. + Assert.Equal("NS.C(Of Integer).S(Of Long)", variableType.ToDisplayString()) + Assert.Equal("Global.NS.C(Of Integer).S(Of Long)", variableType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)) + End Sub + + + + Public Sub OverloadBindingDetermination() + Dim source = +Imports System + +Public Class Program + Private Function Identity (a As Integer) + Return a + End Function + + Private Function Identity (a As Char) + Return a + End Function + + Public Sub Main() + Dim v1 = Identity(3) + Dim v2 = Identity("a"C) + Dim v3 = Identity("arg1") + End Sub +End Class.Value + + Dim tree = SyntaxFactory.ParseSyntaxTree(source) + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib}, options:=vbOptions) + Dim model = comp.GetSemanticModel(tree) + + Dim allInvocations = tree.GetRoot().DescendantNodes().OfType(Of InvocationExpressionSyntax) + + ' Below, we expect to find that the method taking an Integer was selected. + ' We can confidently index into the invocations because we are following the source line-by-line. This is not always a safe practice. + Dim intInvocation = allInvocations.ElementAt(0) + Dim info = model.GetSymbolInfo(intInvocation) + Assert.NotNull(info.Symbol) + Assert.Equal("Private Function Identity(a As Integer) As Object", info.Symbol.ToDisplayString) + + ' Below, we expect to find that the method taking a Char was selected. + Dim charInvocation = allInvocations.ElementAt(1) + info = model.GetSymbolInfo(charInvocation) + Assert.NotNull(info.Symbol) + Assert.Equal("Private Function Identity(a As Char) As Object", info.Symbol.ToDisplayString) + + ' Below, we expect to find that no suitable Method was found, and therefore none were selected. + Dim stringInvocation = allInvocations.ElementAt(2) + info = model.GetSymbolInfo(stringInvocation) + Assert.Null(info.Symbol) + Assert.Equal(2, info.CandidateSymbols.Length) + Assert.Equal(CandidateReason.OverloadResolutionFailure, info.CandidateReason) + End Sub + + + + Public Sub ClassifyConversionFromAnExpressionToATypeSymbol() + Dim source = + +Imports System + +Module Program + + Sub M() + End Sub + + Sub M(l As Long) + End Sub + + Sub M(s As Short) + End Sub + + Sub M(i As Integer) + End Sub + + Sub Main() + Dim ii As Integer = 0 + Console.WriteLine(ii) + Dim jj As Short = 1 + Console.WriteLine(jj) + Dim ss As String = String.Empty + Console.WriteLine(ss) + + ' Perform conversion classification here. + End Sub +End Module +.Value + + Dim tree = SyntaxFactory.ParseSyntaxTree(source) + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib}, options:=vbOptions) + + Dim model = comp.GetSemanticModel(tree) + + ' Get ModifiedIdentifierSyntax corresponding to variable 'ii' in 'Dim ii ...' above. + Dim modifiedIdentifier As ModifiedIdentifierSyntax = CType(tree.GetRoot().FindToken(source.IndexOf("ii")).Parent.Parent, VariableDeclaratorSyntax).Names.Single + + ' Get TypeSymbol corresponding to above ModifiedIdentifierSyntax. + Dim targetType = CType(model.GetDeclaredSymbol(modifiedIdentifier), ILocalSymbol).Type + + ' Perform ClassifyConversion for expressions from within the above SyntaxTree. + Dim sourceExpression1 = CType(tree.GetRoot().FindToken(source.IndexOf("jj)")).Parent, ExpressionSyntax) + Dim conversion As Conversion = model.ClassifyConversion(sourceExpression1, targetType) + + Assert.True(conversion.IsWidening AndAlso conversion.IsNumeric) + + Dim sourceExpression2 = CType(tree.GetRoot().FindToken(source.IndexOf("ss)")).Parent, ExpressionSyntax) + conversion = model.ClassifyConversion(sourceExpression2, targetType) + + Assert.True(conversion.IsNarrowing AndAlso conversion.IsString) + + ' Perform ClassifyConversion for constructed expressions + ' at the position identified by the comment "' Perform ..." above. + Dim sourceExpression3 As ExpressionSyntax = SyntaxFactory.IdentifierName("jj") + Dim position = source.IndexOf("' ") + conversion = model.ClassifyConversion(position, sourceExpression3, targetType) + + Assert.True(conversion.IsWidening AndAlso conversion.IsNumeric) + + Dim sourceExpression4 As ExpressionSyntax = SyntaxFactory.IdentifierName("ss") + conversion = model.ClassifyConversion(position, sourceExpression4, targetType) + + Assert.True(conversion.IsNarrowing AndAlso conversion.IsString) + + Dim sourceExpression5 As ExpressionSyntax = SyntaxFactory.ParseExpression("100L") + conversion = model.ClassifyConversion(position, sourceExpression5, targetType) + + ' This is Widening because the numeric literal constant 100L can be converted to Integer + ' without any data loss. Note: This is special for literal constants. + Assert.True(conversion.IsWidening AndAlso conversion.IsNumeric) + End Sub + + + + Public Sub ClassifyConversionFromOneTypeSymbolToAnother() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Module Program + + Sub Main() + End Sub +End Module +.Value) + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib}, options:=vbOptions) + + Dim int32Type = comp.GetSpecialType(SpecialType.System_Int32) + Dim int16Type = comp.GetSpecialType(SpecialType.System_Int16) + Dim stringType = comp.GetSpecialType(SpecialType.System_String) + Dim int64Type = comp.GetSpecialType(SpecialType.System_Int64) + + Assert.True(comp.ClassifyConversion(int32Type, int32Type).IsIdentity) + + Dim conversion1 = comp.ClassifyConversion(int16Type, int32Type) + + Assert.True(conversion1.IsWidening AndAlso conversion1.IsNumeric) + + Dim conversion2 = comp.ClassifyConversion(stringType, int32Type) + + Assert.True(conversion2.IsNarrowing AndAlso conversion2.IsString) + + Dim conversion3 = comp.ClassifyConversion(int64Type, int32Type) + + Assert.True(conversion3.IsNarrowing AndAlso conversion3.IsNumeric) + End Sub + + + + Public Sub GetTargetFrameworkVersionForCompilation() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Module Program + + Sub Main() + End Sub +End Module +.Value) + + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib}, options:=vbOptions) + + Dim version As Version = comp.GetSpecialType(SpecialType.System_Object).ContainingAssembly.Identity.Version + Assert.Equal(4, version.Major) + End Sub + + + + Public Sub GetAssemblySymbolsAndSyntaxTreesFromAProject() + Dim source = +Module Program + + Sub Main() + End Sub +End Module +.Value + + Dim _projectId = ProjectId.CreateNewId() + Dim _documentId = DocumentId.CreateNewId(_projectId) + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + + Dim sln = New AdhocWorkspace().CurrentSolution. + AddProject(_projectId, "MyProject", "MyProject", LanguageNames.VisualBasic).WithProjectCompilationOptions(_projectId, vbOptions). + AddMetadataReference(_projectId, Mscorlib). + AddDocument(_documentId, "MyFile.vb", source) + + ' If you wish to try against a real project you could use code like + ' Dim project = Solution.LoadStandaloneProject("") + ' OR Dim project = Workspace.LoadStandaloneProject("").CurrentSolution.Projects.First + Dim project = sln.Projects.Single + Dim compilation = project.GetCompilationAsync().Result + + ' Get AssemblySymbols for above compilation and the first assembly (Mscorlib) referenced by it. + Dim compilationAssembly As IAssemblySymbol = compilation.Assembly + Dim referencedAssembly As IAssemblySymbol = DirectCast(compilation.GetAssemblyOrModuleSymbol(project.MetadataReferences.First), IAssemblySymbol) + + Assert.True(compilation.GetTypeByMetadataName("Program").ContainingAssembly.Equals(compilationAssembly)) + Assert.True(compilation.GetTypeByMetadataName("System.Object").ContainingAssembly.Equals(referencedAssembly)) + + Dim tree As SyntaxTree = project.Documents.Single.GetSyntaxTreeAsync().Result + + Assert.Equal("MyFile.vb", tree.FilePath) + + End Sub + + + + Public Sub UseSyntaxAnnotations() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Imports System + +Module Program + + Sub Main() + Dim i As Integer = 0 + Console.WriteLine(i) + End Sub +End Module +.Value) + + ' Tag all tokens that contain the letter 'i'. + Dim rewriter = New MyAnnotator() + Dim oldRoot As SyntaxNode = tree.GetRoot() + Dim newRoot As SyntaxNode = rewriter.Visit(oldRoot) + + Assert.False(oldRoot.ContainsAnnotations) + Assert.True(newRoot.ContainsAnnotations) + + ' Find all tokens that were tagged with annotations of type MyAnnotation. + Dim annotatedTokens As IEnumerable(Of SyntaxNodeOrToken) = newRoot.GetAnnotatedNodesAndTokens(MyAnnotation.Kind) + Dim results = String.Join(vbLf, annotatedTokens.Select(Function(nodeOrToken) + Assert.True(nodeOrToken.IsToken) + Dim annotation = nodeOrToken.GetAnnotations(MyAnnotation.Kind).Single + Return String.Format("{0} (position {1})", nodeOrToken.ToString(), MyAnnotation.GetPosition(annotation)) + End Function)) + + Assert.Equal( +Main (position 2) +Dim (position 1) +i (position 0) +WriteLine (position 2) +i (position 0).Value, results) + End Sub + + ' Below VisualBasicSyntaxRewriter tags all SyntaxTokens that contain the lowercase letter 'i' under the SyntaxNode being visited. + Public Class MyAnnotator + Inherits VisualBasicSyntaxRewriter + + Public Overrides Function VisitToken(token As SyntaxToken) As SyntaxToken + Dim newToken = MyBase.VisitToken(token) + Dim position = token.ToString().IndexOf("i"c) + If position >= 0 Then + newToken = newToken.WithAdditionalAnnotations(MyAnnotation.Create(position)) + End If + + Return newToken + End Function + End Class + + Public Class MyAnnotation + Public Const Kind As String = "MyAnnotation" + + Public Shared Function Create(position As Integer) As SyntaxAnnotation + Return New SyntaxAnnotation(Kind, position.ToString()) + End Function + + Public Shared Function GetPosition(annotation As SyntaxAnnotation) As Integer + Return Integer.Parse(annotation.Data) + End Function + End Class + + + + Public Sub GetBaseTypesAndOverridingRelationships() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Imports System +MustInherit Class C1 + Public Overridable Function F1(s As Short) As Integer + Return 0 + End Function + + Public MustOverride Property P1 As Integer +End Class + +MustInherit Class C2 + Inherits C1 + + Public Shadows Overridable Function F1(s As Short) As Integer + Return 1 + End Function +End Class + +Class C3 + Inherits C2 + + Public Overrides NotOverridable Function F1(s As Short) As Integer + Return 2 + End Function + + Public Overrides Property P1 As Integer +End Class +.Value) + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithEmbedVbCoreRuntime(True) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib}, options:=vbOptions) + + ' Get TypeSymbols for types C1, C2 and C3 above. + Dim typeC1 = comp.GetTypeByMetadataName("C1") + Dim typeC2 = comp.GetTypeByMetadataName("C2") + Dim typeC3 = comp.GetTypeByMetadataName("C3") + Dim typeObject = comp.GetSpecialType(SpecialType.System_Object) + + ' Get TypeSymbols for base types of C1, C2 and C3 above. + Assert.True(typeC1.BaseType.Equals(typeObject)) + Assert.True(typeC2.BaseType.Equals(typeC1)) + Assert.True(typeC3.BaseType.Equals(typeC2)) + + ' Get MethodSymbols for methods named F1 in types C1, C2 and C3 above. + Dim methodC1F1 = CType(typeC1.GetMembers("F1").Single(), IMethodSymbol) + Dim methodC2F1 = CType(typeC2.GetMembers("F1").Single(), IMethodSymbol) + Dim methodC3F1 = CType(typeC3.GetMembers("F1").Single(), IMethodSymbol) + + ' Get overriding relationships between above MethodSymbols. + Assert.True(methodC1F1.IsOverridable) + Assert.True(methodC2F1.IsOverridable) + Assert.False(methodC2F1.IsOverrides) + Assert.True(methodC3F1.IsOverrides) + Assert.True(methodC3F1.IsNotOverridable) + Assert.True(methodC3F1.OverriddenMethod.Equals(methodC2F1)) + Assert.False(methodC3F1.OverriddenMethod.Equals(methodC1F1)) + + ' Get PropertySymbols for properties named P1 in types C1 and C3 above. + Dim propertyC1P1 = CType(typeC1.GetMembers("P1").Single(), IPropertySymbol) + Dim propertyC3P1 = CType(typeC3.GetMembers("P1").Single(), IPropertySymbol) + + ' Get overriding relationships between above PropertySymbols. + Assert.True(propertyC1P1.IsMustOverride) + Assert.False(propertyC1P1.IsOverridable) + Assert.True(propertyC3P1.IsOverrides) + Assert.True(propertyC3P1.OverriddenProperty.Equals(propertyC1P1)) + End Sub + + + + Public Sub GetInterfacesAndImplementationRelationships() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Imports System +Interface I1 + Sub M1() + Property P1 As Integer +End Interface + +Interface I2 + Inherits I1 + Sub M2() +End Interface + +Class C1 + Implements I1 + + Public Sub M1() Implements I1.M1 + End Sub + + Public Overridable Property P1 As Integer Implements I1.P1 +End Class + +Class C2 + Inherits C1 + Implements I2 + + Shadows Public Sub M1() Implements I1.M1 + End Sub + + Public Sub M2() Implements I2.M2 + End Sub +End Class + +Class C3 + Inherits C2 + Implements I1 + + Public Overrides Property P1 As Integer Implements I1.P1 +End Class +.Value) + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithEmbedVbCoreRuntime(True) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib}, options:=vbOptions) + + ' Get TypeSymbols for types I1, I2, C1, C2 and C3 above. + Dim typeI1 = comp.GetTypeByMetadataName("I1") + Dim typeI2 = comp.GetTypeByMetadataName("I2") + Dim typeC1 = comp.GetTypeByMetadataName("C1") + Dim typeC2 = comp.GetTypeByMetadataName("C2") + Dim typeC3 = comp.GetTypeByMetadataName("C3") + + Assert.Null(typeI1.BaseType) + Assert.Null(typeI2.BaseType) + Assert.Equal(0, typeI1.Interfaces.Count) + Assert.True(typeI2.Interfaces.Single().Equals(typeI1)) + + ' Get TypeSymbol for interface implemented by C1 above. + Assert.True(typeC1.Interfaces.Single().Equals(typeI1)) + + ' Get TypeSymbols for interfaces implemented by C2 above. + Assert.True(typeC2.Interfaces.Single().Equals(typeI2)) + Assert.Equal(2, typeC2.AllInterfaces.Count) + Assert.NotNull(Aggregate type In typeC2.AllInterfaces + Where type.Equals(typeI1) + Into [Single]()) + Assert.NotNull(Aggregate type In typeC2.AllInterfaces + Where type.Equals(typeI2) + Into [Single]()) + + ' Get TypeSymbols for interfaces implemented by C3 above. + Assert.True(typeC3.Interfaces.Single().Equals(typeI1)) + Assert.Equal(2, typeC3.AllInterfaces.Count) + Assert.NotNull(Aggregate type In typeC3.AllInterfaces + Where type.Equals(typeI1) + Into [Single]()) + Assert.NotNull(Aggregate type In typeC3.AllInterfaces + Where type.Equals(typeI2) + Into [Single]()) + + ' Get MethodSymbols for methods named M1 and M2 in types I1, I2, C1 and C2 above. + Dim methodI1M1 = CType(typeI1.GetMembers("M1").Single(), IMethodSymbol) + Dim methodI2M2 = CType(typeI2.GetMembers("M2").Single(), IMethodSymbol) + Dim methodC1M1 = CType(typeC1.GetMembers("M1").Single(), IMethodSymbol) + Dim methodC2M1 = CType(typeC2.GetMembers("M1").Single(), IMethodSymbol) + Dim methodC2M2 = CType(typeC2.GetMembers("M2").Single(), IMethodSymbol) + + ' Get interface implementation relationships between above MethodSymbols. + Assert.True(typeC1.FindImplementationForInterfaceMember(methodI1M1).Equals(methodC1M1)) + Assert.True(typeC2.FindImplementationForInterfaceMember(methodI1M1).Equals(methodC2M1)) + Assert.True(typeC2.FindImplementationForInterfaceMember(methodI2M2).Equals(methodC2M2)) + Assert.True(typeC3.FindImplementationForInterfaceMember(methodI1M1).Equals(methodC2M1)) + Assert.True(typeC3.FindImplementationForInterfaceMember(methodI2M2).Equals(methodC2M2)) + + Assert.True(methodC1M1.ExplicitInterfaceImplementations.Single().Equals(methodI1M1)) + Assert.True(methodC2M1.ExplicitInterfaceImplementations.Single().Equals(methodI1M1)) + Assert.True(methodC2M2.ExplicitInterfaceImplementations.Single().Equals(methodI2M2)) + + ' Get PropertySymbols for properties named P1 in types I1, C1 and C3 above. + Dim propertyI1P1 = CType(typeI1.GetMembers("P1").Single(), IPropertySymbol) + Dim propertyC1P1 = CType(typeC1.GetMembers("P1").Single(), IPropertySymbol) + Dim propertyC3P1 = CType(typeC3.GetMembers("P1").Single(), IPropertySymbol) + + ' Get interface implementation relationships between above PropertySymbols. + Assert.True(typeC1.FindImplementationForInterfaceMember(propertyI1P1).Equals(propertyC1P1)) + Assert.True(typeC2.FindImplementationForInterfaceMember(propertyI1P1).Equals(propertyC1P1)) + Assert.True(typeC3.FindImplementationForInterfaceMember(propertyI1P1).Equals(propertyC3P1)) + + Assert.True(propertyC1P1.ExplicitInterfaceImplementations.Single.Equals(propertyI1P1)) + Assert.True(propertyC3P1.ExplicitInterfaceImplementations.Single.Equals(propertyI1P1)) + End Sub + + + + Public Sub GetAppliedAttributes() + Dim source = +Imports System +Module Module1 + <AttributeUsage(AttributeTargets.Method)> + Private Class ExampleAttribute + Inherits Attribute + + Private ReadOnly _Id As Integer + + Public ReadOnly Property Id As Integer + Get + Return _Id + End Get + End Property + + Public Sub New(id As Integer) + Me._Id = id + End Sub + End Class + + Sub Method1() + ' Intentionally left blank + End Sub + + <ExampleAttribute(1)> + Sub Method2() + ' Intentionally left blank + End Sub + + <ExampleAttribute(2)> + Sub Method3() + ' Intentionally left blank + End Sub +End Module + .Value + Dim tree = SyntaxFactory.ParseSyntaxTree(source) + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithEmbedVbCoreRuntime(True) + Dim compilation = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib}, options:=vbOptions) + + Dim model = compilation.GetSemanticModel(tree) + + Dim getMethod As Func(Of String, IMethodSymbol) = Function(name) Aggregate declaration In tree.GetRoot().DescendantNodes().OfType(Of MethodStatementSyntax) + Where 0 = String.Compare(name, declaration.Identifier.Text, True) + Select model.GetDeclaredSymbol(declaration) + Into [Single] + + Dim methodSymbol As IMethodSymbol + Dim attributeSymbol As INamedTypeSymbol + Dim appliedAttribute As AttributeData + + attributeSymbol = Aggregate declaration In tree.GetRoot().DescendantNodes().OfType(Of ClassStatementSyntax) + Where 0 = String.Compare(declaration.Identifier.Text, "ExampleAttribute", True) + Select model.GetDeclaredSymbol(declaration) Into [Single] + + ' Verify that a method has no attributes + methodSymbol = getMethod("Method1") + Assert.Equal(0, methodSymbol.GetAttributes().Count) + + ' Inspect the attributes that have been given to methods 2 and 3 + methodSymbol = getMethod("Method2") + appliedAttribute = methodSymbol.GetAttributes().Single + Assert.Equal(attributeSymbol, appliedAttribute.AttributeClass) + Assert.Equal(TypedConstantKind.Primitive, appliedAttribute.ConstructorArguments(0).Kind) + Assert.Equal(1, CType(appliedAttribute.ConstructorArguments(0).Value, Integer)) + + methodSymbol = getMethod("Method3") + appliedAttribute = methodSymbol.GetAttributes().Single + Assert.Equal(attributeSymbol, appliedAttribute.AttributeClass) + Assert.Equal(TypedConstantKind.Primitive, appliedAttribute.ConstructorArguments(0).Kind) + Assert.Equal(2, CType(appliedAttribute.ConstructorArguments(0).Value, Integer)) + End Sub +#End Region + +#Region " Section 2 : Constructing & Updating Tree Questions " + + + + Public Sub AddMethodToClass() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Class C + +End Class +.Value) + + Dim compilationUnit = CType(tree.GetRoot(), CompilationUnitSyntax) + + ' Get ClassBlockSyntax corresponding to 'Class C' above. + Dim classDeclaration As ClassBlockSyntax = compilationUnit.ChildNodes.OfType(Of ClassBlockSyntax).Single + + ' Construct a new MethodBlockSyntax. + Dim newMethodDeclaration As MethodBlockSyntax = SyntaxFactory.SubBlock(SyntaxFactory.SubStatement("M")) + + ' Add this new MethodBlockSyntax to the above ClassBlockSyntax. + Dim newClassDeclaration As ClassBlockSyntax = classDeclaration.AddMembers(newMethodDeclaration) + + ' Update the CompilationUnitSyntax with the new ClassBlockSyntax. + Dim newCompilationUnit As CompilationUnitSyntax = compilationUnit.ReplaceNode(classDeclaration, newClassDeclaration) + + ' Format the new CompilationUnitSyntax. + newCompilationUnit = newCompilationUnit.NormalizeWhitespace(" ") + + Dim expected = +Class C + + Sub M + End Sub +End Class +.Value + + Assert.Equal(expected, newCompilationUnit.ToFullString().Replace(vbCrLf, vbLf)) + End Sub + + + + Public Sub ReplaceSubExpression() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Module Program + + Sub Main() + Dim i As Integer = 0, j As Integer = 0 + Console.WriteLine((i + j) - (i + j)) + End Sub +End Module +.Value) + + Dim compilationUnit = CType(tree.GetRoot(), CompilationUnitSyntax) + + ' Get BinaryExpressionSyntax corresponding to the two addition expressions 'i + j' above. + Dim addExpression1 As BinaryExpressionSyntax = compilationUnit.DescendantNodes. + OfType(Of BinaryExpressionSyntax). + First(Function(b) b.Kind() = SyntaxKind.AddExpression) + + Dim addExpression2 As BinaryExpressionSyntax = compilationUnit.DescendantNodes. + OfType(Of BinaryExpressionSyntax). + Last(Function(b) b.Kind() = SyntaxKind.AddExpression) + + ' Replace addition expressions 'i + j' with multiplication expressions 'i * j'. + Dim multipyExpression1 As BinaryExpressionSyntax = + SyntaxFactory.MultiplyExpression(addExpression1.Left, + SyntaxFactory.Token(SyntaxKind.AsteriskToken).WithLeadingTrivia(addExpression1.OperatorToken.LeadingTrivia). + WithTrailingTrivia(addExpression1.OperatorToken.TrailingTrivia), + addExpression1.Right) + + Dim multipyExpression2 As BinaryExpressionSyntax = + SyntaxFactory.MultiplyExpression(addExpression2.Left, + SyntaxFactory.Token(SyntaxKind.AsteriskToken).WithLeadingTrivia(addExpression2.OperatorToken.LeadingTrivia). + WithTrailingTrivia(addExpression2.OperatorToken.TrailingTrivia), + addExpression2.Right) + + Dim newCompilationUnit As CompilationUnitSyntax = + compilationUnit.ReplaceNodes(nodes:={addExpression1, addExpression2}, + computeReplacementNode:=Function(originalNode, originalNodeWithReplacedDescendants) + Dim newNode As SyntaxNode = Nothing + If originalNode Is addExpression1 Then + newNode = multipyExpression1 + ElseIf originalNode Is addExpression2 Then + newNode = multipyExpression2 + End If + + Return newNode + End Function) + Assert.Equal( + +Module Program + + Sub Main() + Dim i As Integer = 0, j As Integer = 0 + Console.WriteLine((i * j) - (i * j)) + End Sub +End Module +.Value, newCompilationUnit.ToFullString()) + End Sub + + + + Public Sub UseSymbolicInformationPlusRewriterToMakeCodeChanges() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Imports System + +Module Program + + Sub Main() + Dim x As New C() + C.ReferenceEquals(x, x) + End Sub +End Module + +Class C + + Dim y As C = Nothing + + Public Sub New() + y = New C() + End Sub +End Class +.Value) + + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib}, options:=vbOptions) + + Dim model = comp.GetSemanticModel(tree) + + ' Get the ClassBlockSyntax corresponding to 'Class C' above. + Dim classDeclaration As ClassBlockSyntax = tree.GetRoot().DescendantNodes. + OfType(Of ClassBlockSyntax). + Single(Function(c) c.ClassStatement.Identifier.ToString() = "C") + + ' Get Symbol corresponding to class C above. + Dim searchSymbol = model.GetDeclaredSymbol(classDeclaration) + Dim oldRoot As SyntaxNode = tree.GetRoot() + Dim rewriter = New ClassRenamer() With {.SearchSymbol = searchSymbol, .SemanticModel = model, .NewName = "C1"} + Dim newRoot As SyntaxNode = rewriter.Visit(oldRoot) + + Assert.Equal( + +Imports System + +Module Program + + Sub Main() + Dim x As New C1() + C1.ReferenceEquals(x, x) + End Sub +End Module + +Class C1 + + Dim y As C1 = Nothing + + Public Sub New() + y = New C1() + End Sub +End Class +.Value, newRoot.ToFullString()) + End Sub + + ' Below VisualBasicSyntaxRewriter renames multiple occurrences of a particular class name under the SyntaxNode being visited. + ' Note that the below rewriter is not a full / correct implementation of symbolic rename. For example, it doesn't + ' handle aliases etc. A full implementation for symbolic rename would be more complicated and is + ' beyond the scope of this sample. The intent of this sample is mainly to demonstrate how symbolic info can be used + ' in conjunction a rewriter to make syntactic changes. + Public Class ClassRenamer + Inherits VisualBasicSyntaxRewriter + Public Property SearchSymbol As ITypeSymbol + + Public Property SemanticModel As SemanticModel + + Public Property NewName As String + + ' Replace old ClassStatementSyntax with new one. + Public Overrides Function VisitClassStatement(node As ClassStatementSyntax) As SyntaxNode + + Dim updatedClassStatement = CType(MyBase.VisitClassStatement(node), ClassStatementSyntax) + + ' Get TypeSymbol corresponding to the ClassBlockSyntax and check whether + ' it is the same as the TypeSymbol we are searching for. + Dim classSymbol = SemanticModel.GetDeclaredSymbol(node) + If classSymbol.Equals(SearchSymbol) Then + + ' Replace the identifier token containing the name of the class. + Dim updatedIdentifierToken As SyntaxToken = + SyntaxFactory.Identifier(updatedClassStatement.Identifier.LeadingTrivia, NewName, updatedClassStatement.Identifier.TrailingTrivia) + + updatedClassStatement = updatedClassStatement.WithIdentifier(updatedIdentifierToken) + End If + + Return updatedClassStatement + End Function + + ' Replace all occurrences of old class name with new one. + Public Overrides Function VisitIdentifierName(node As IdentifierNameSyntax) As SyntaxNode + Dim updatedIdentifierName = CType(MyBase.VisitIdentifierName(node), IdentifierNameSyntax) + + ' Get TypeSymbol corresponding to the IdentifierNameSyntax and check whether + ' it is the same as the TypeSymbol we are searching for. + Dim identifierSymbol = SemanticModel.GetSymbolInfo(node).Symbol + + ' Handle Dim x As |C| = New C(). + Dim isMatchingTypeName = identifierSymbol.Equals(SearchSymbol) + + ' Handle Dim x As C = New |C|(). + Dim isMatchingConstructor = TypeOf identifierSymbol Is IMethodSymbol AndAlso + CType(identifierSymbol, IMethodSymbol).MethodKind = MethodKind.Constructor AndAlso + identifierSymbol.ContainingSymbol.Equals(SearchSymbol) + + If isMatchingTypeName OrElse isMatchingConstructor Then + + ' Replace the identifier token containing the name of the class. + Dim updatedIdentifierToken As SyntaxToken = SyntaxFactory.Identifier(updatedIdentifierName.Identifier.LeadingTrivia, NewName, updatedIdentifierName.Identifier.TrailingTrivia) + + updatedIdentifierName = updatedIdentifierName.WithIdentifier(updatedIdentifierToken) + End If + + Return updatedIdentifierName + End Function + End Class + + + + Public Sub DeleteAssignmentStatementsFromASyntaxTree() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Module Program + + Sub Main() + Dim x As Integer = 1 + x = 2 + If True Then + x = 3 + Else + x = 4 + End If + End Sub +End Module +.Value) + + Dim oldRoot As SyntaxNode = tree.GetRoot() + ' If the assignment statement has a parent block, it is ok to remove the assignment statement completely. + ' However, if the parent context is some statement like a single-line if statement without a block, + ' removing the assignment statement would result in the parent statement becoming incomplete and + ' would produce code that doesn't compile - so we leave this case unhandled. + Dim nodesToRemove = From node In oldRoot.DescendantNodes().OfType(Of AssignmentStatementSyntax)() + Where IsBlock(node.Parent) + Dim newRoot As SyntaxNode = oldRoot.RemoveNodes(nodesToRemove, SyntaxRemoveOptions.KeepNoTrivia) + Assert.Equal( +Module Program + + Sub Main() + Dim x As Integer = 1 + If True Then + Else + End If + End Sub +End Module +.Value, newRoot.ToFullString()) + End Sub + + Private Shared Function IsBlock(node As SyntaxNode) As Boolean + If node IsNot Nothing Then + If TypeOf node Is MethodBlockBaseSyntax OrElse + TypeOf node Is DoLoopBlockSyntax OrElse + TypeOf node Is ForOrForEachBlockSyntax OrElse + TypeOf node Is MultiLineLambdaExpressionSyntax Then + + Return True + End If + + Select Case node.Kind + Case SyntaxKind.WhileBlock, + SyntaxKind.UsingBlock, + SyntaxKind.SyncLockBlock, + SyntaxKind.WithBlock, + SyntaxKind.MultiLineIfBlock, + SyntaxKind.ElseBlock, + SyntaxKind.TryBlock, + SyntaxKind.CatchBlock, + SyntaxKind.FinallyBlock, + SyntaxKind.CaseBlock + + Return True + End Select + End If + + Return False + End Function + + + + Public Sub ConstructArrayType() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Module Program + + Sub Main() + End Sub +End Module +.Value) + + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib}, options:=vbOptions) + Dim elementType = comp.GetSpecialType(SpecialType.System_Int32) + + Dim arrayType = comp.CreateArrayTypeSymbol(elementType, rank:=3) + Assert.Equal("Integer(*,*,*)", arrayType.ToDisplayString()) + End Sub + + + + Public Sub DeleteRegionsUsingRewriter() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Imports System + +#Region " Program " +Module Program + + Sub Main() + End Sub +End Module +#End Region + +#Region " Other " +Class C + +End Class +#End Region.Value) + + Dim oldRoot As SyntaxNode = tree.GetRoot() + Dim expected = + +Imports System + +Module Program + + Sub Main() + End Sub +End Module + +Class C + +End Class +.Value + + Dim rewriter As VisualBasicSyntaxRewriter = New RegionRemover1() + Dim newRoot As SyntaxNode = rewriter.Visit(oldRoot) + + Assert.Equal(expected, newRoot.ToFullString()) + + rewriter = New RegionRemover2() + newRoot = rewriter.Visit(oldRoot) + + Assert.Equal(expected, newRoot.ToFullString()) + End Sub + + ' Below VisualBasicSyntaxRewriter removes all #Regions and #End Regions from under the SyntaxNode being visited. + Public Class RegionRemover1 + Inherits VisualBasicSyntaxRewriter + + Public Overrides Function VisitTrivia(trivia As SyntaxTrivia) As SyntaxTrivia + Dim updatedTrivia As SyntaxTrivia = MyBase.VisitTrivia(trivia) + Dim directiveTrivia = TryCast(trivia.GetStructure(), DirectiveTriviaSyntax) + If directiveTrivia IsNot Nothing Then + If directiveTrivia.Kind() = SyntaxKind.RegionDirectiveTrivia OrElse directiveTrivia.Kind() = SyntaxKind.EndRegionDirectiveTrivia Then + updatedTrivia = Nothing + End If + End If + + Return updatedTrivia + End Function + End Class + + ' Below VisualBasicSyntaxRewriter removes all #Regions and #End Regions from under the SyntaxNode being visited. + Public Class RegionRemover2 + Inherits VisualBasicSyntaxRewriter + + Public Overrides Function VisitToken(token As SyntaxToken) As SyntaxToken + ' Remove all #Regions and #End Regions from underneath the token. + Return token.WithLeadingTrivia(RemoveRegions(token.LeadingTrivia)). + WithTrailingTrivia(RemoveRegions(token.TrailingTrivia)) + End Function + + Private Function RemoveRegions(oldTriviaList As SyntaxTriviaList) As SyntaxTriviaList + Return SyntaxFactory.TriviaList(From trivia In oldTriviaList + Where trivia.Kind() <> SyntaxKind.RegionDirectiveTrivia AndAlso trivia.Kind() <> SyntaxKind.EndRegionDirectiveTrivia) + End Function + End Class + + + + Public Sub DeleteRegions() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Imports System + +#Region " Program " +Module Program + + Sub Main() + End Sub +End Module +#End Region + +#Region " Other " +Class C + +End Class +#End Region.Value) + + Dim oldRoot As SyntaxNode = tree.GetRoot() + + ' Get all RegionDirective and EndRegionDirective trivia. + Dim trivia As IEnumerable(Of SyntaxTrivia) = + From t In oldRoot.DescendantTrivia + Where t.Kind() = SyntaxKind.RegionDirectiveTrivia OrElse + t.Kind() = SyntaxKind.EndRegionDirectiveTrivia + + Dim newRoot As SyntaxNode = + oldRoot.ReplaceTrivia(trivia:=trivia, + computeReplacementTrivia:=Function(originalTrivia, originalTriviaWithReplacedDescendants) Nothing) + Assert.Equal( + +Imports System + +Module Program + + Sub Main() + End Sub +End Module + +Class C + +End Class +.Value, newRoot.ToFullString()) + End Sub + + + + Public Sub InsertLoggingStatements() + Dim tree = SyntaxFactory.ParseSyntaxTree( + +Module Program + + Sub Main() + System.Console.WriteLine() + Dim total As Integer = 0 + For i As Integer = 0 To 4 + total += i + Next + + If True Then + total += 5 + End If + End Sub +End Module +.Value) + + Dim oldRoot As CompilationUnitSyntax = tree.GetCompilationUnitRoot() + Dim rewriter = New ConsoleWriteLineInserter() + Dim newRoot = CType(rewriter.Visit(oldRoot), CompilationUnitSyntax) + newRoot = newRoot.NormalizeWhitespace() ' normalize all the whitespace to make it legible + Dim newTree = tree.WithRootAndOptions(newRoot, tree.Options) + Dim systemRuntimeReference = MetadataReference.CreateFromFile(AppDomain.CurrentDomain.GetAssemblies().Single(Function(assembly) assembly.GetName().Name = "System.Runtime").Location) + Dim vbRuntime = MetadataReference.CreateFromFile(GetType(CompilerServices.StandardModuleAttribute).Assembly.Location) + Dim consoleReference = MetadataReference.CreateFromFile(GetType(Console).Assembly.Location) + Dim comp = VisualBasicCompilation.Create("MyCompilation", syntaxTrees:={newTree}, references:={Mscorlib, systemRuntimeReference, vbRuntime, consoleReference}) + comp = comp.WithOptions(comp.Options.WithEmbedVbCoreRuntime(True)) + Dim output As String = Execute(comp) + + Assert.Equal( + +0 +1 +3 +6 +10 +15 +.Value, output.Replace(vbCrLf, vbLf)) + End Sub + + ' Below VisualBasicSyntaxRewriter inserts a Console.WriteLine() statement to print the value of the + ' LHS variable for compound assignment statements encountered in the input tree. + Public Class ConsoleWriteLineInserter + Inherits VisualBasicSyntaxRewriter + + Public Overrides Function VisitAssignmentStatement(node As AssignmentStatementSyntax) As SyntaxNode + Dim updatedNode As SyntaxNode = MyBase.VisitAssignmentStatement(node) + + If IsBlock(node.Parent) AndAlso + (node.Kind() = SyntaxKind.AddAssignmentStatement OrElse + node.Kind() = SyntaxKind.SubtractAssignmentStatement OrElse + node.Kind() = SyntaxKind.MultiplyAssignmentStatement OrElse + node.Kind() = SyntaxKind.DivideAssignmentStatement) Then + + ' Print value of the variable on the 'Left' side of + ' compound assignment statements encountered. + Dim consoleWriteLineStatement As StatementSyntax = + SyntaxFactory.ParseExecutableStatement(String.Format("System.Console.WriteLine({0})", node.Left.ToString())) + Dim statementPair = + SyntaxFactory.List(Of StatementSyntax)({ + node.WithLeadingTrivia().WithTrailingTrivia(), + consoleWriteLineStatement}) + + updatedNode = + SyntaxFactory.MultiLineIfBlock( + SyntaxFactory.IfStatement( + SyntaxFactory.Token(SyntaxKind.IfKeyword), + SyntaxFactory.TrueLiteralExpression(SyntaxFactory.Token(SyntaxKind.TrueKeyword)), + SyntaxFactory.Token(SyntaxKind.ThenKeyword) + ), + statementPair, + Nothing, + Nothing). + WithLeadingTrivia(node.GetLeadingTrivia()). + WithTrailingTrivia(node.GetTrailingTrivia()) ' Attach leading and trailing trivia (that we removed from the original node above) to the updated node. + End If + + Return updatedNode + End Function + End Class + + ' A simple helper to execute the code present inside a compilation. + Public Function Execute(comp As Compilation) As String + Dim output = New StringBuilder() + Dim exeFilename As String = "OutputVB.exe", pdbFilename As String = "OutputVB.pdb", xmlCommentsFilename As String = "OutputVB.xml" + Dim emitResult As Microsoft.CodeAnalysis.Emit.EmitResult = Nothing + + Using ilStream = New FileStream(exeFilename, FileMode.OpenOrCreate), + pdbStream = New FileStream(pdbFilename, FileMode.OpenOrCreate), + xmlCommentsStream = New FileStream(xmlCommentsFilename, FileMode.OpenOrCreate) + ' Emit IL, PDB and xml documentation comments for the compilation to disk. + emitResult = comp.Emit(ilStream, pdbStream, xmlCommentsStream) + End Using + + If emitResult.Success Then + Dim p = Process.Start(New ProcessStartInfo() With {.FileName = exeFilename, .UseShellExecute = False, .RedirectStandardOutput = True, .RedirectStandardError = True}) + output.Append(p.StandardOutput.ReadToEnd()) + output.Append(p.StandardError.ReadToEnd()) + p.WaitForExit() + Else + output.AppendLine("Errors:") + For Each diag In emitResult.Diagnostics + output.AppendLine(diag.ToString()) + Next + + End If + + Return output.ToString() + End Function + + Private Class SimplifyAnnotationRewriter + Inherits VisualBasicSyntaxRewriter + + Private Function AnnotateNodeWithSimplifyAnnotation(node As SyntaxNode) As SyntaxNode + Return node.WithAdditionalAnnotations(Simplifier.Annotation, Formatter.Annotation) + End Function + + Public Overrides Function VisitQualifiedName(node As QualifiedNameSyntax) As SyntaxNode + Return AnnotateNodeWithSimplifyAnnotation(node) + End Function + + Public Overrides Function VisitMemberAccessExpression(node As MemberAccessExpressionSyntax) As SyntaxNode + Return AnnotateNodeWithSimplifyAnnotation(node) + End Function + + Public Overrides Function VisitIdentifierName(node As IdentifierNameSyntax) As SyntaxNode + Return AnnotateNodeWithSimplifyAnnotation(node) + End Function + + Public Overrides Function VisitGenericName(node As GenericNameSyntax) As SyntaxNode + Return AnnotateNodeWithSimplifyAnnotation(node) + End Function + End Class + + + + Public Sub UseServices() + Dim source = +Imports System.Diagnostics +Imports System +Imports System.IO + +Namespace NS + +Public Class C + +End Class +End Namespace + +Module Program + + Public Sub Main() + Dim i As System.Int32 = 0 + System.Console.WriteLine(i.ToString()) + Dim p As Process = Process.GetCurrentProcess() + Console.WriteLine(p.Id) + End Sub +End Module +.Value + + Dim _projectId = ProjectId.CreateNewId() + Dim _documentId = DocumentId.CreateNewId(_projectId) + + Dim systemConsoleReference = MetadataReference.CreateFromFile(GetType(Console).Assembly.Location) + + Dim vbOptions = New VisualBasicCompilationOptions(OutputKind.ConsoleApplication).WithEmbedVbCoreRuntime(True) + + Dim sln = New AdhocWorkspace().CurrentSolution. + AddProject(_projectId, "MyProject", "MyProject", LanguageNames.VisualBasic).WithProjectCompilationOptions(_projectId, vbOptions). + AddMetadataReference(_projectId, Mscorlib). + AddMetadataReference(_projectId, systemConsoleReference). + AddDocument(_documentId, "MyFile.vb", source) + + ' Format the document. + Dim document = sln.GetDocument(_documentId) + document = Formatter.FormatAsync(document).Result + + Assert.Equal( +Imports System.Diagnostics +Imports System +Imports System.IO + +Namespace NS + + Public Class C + + End Class +End Namespace + +Module Program + + Public Sub Main() + Dim i As System.Int32 = 0 + System.Console.WriteLine(i.ToString()) + Dim p As Process = Process.GetCurrentProcess() + Console.WriteLine(p.Id) + End Sub +End Module +.Value, document.GetSyntaxRootAsync().Result.ToString().Replace(vbCrLf, vbLf)) + + ' Simplify names used in the document i.e. remove unnecessary namespace qualifiers. + Dim newRoot = New SimplifyAnnotationRewriter().Visit(DirectCast(document.GetSyntaxRootAsync().Result, SyntaxNode)) + document = document.WithSyntaxRoot(newRoot) + document = Simplifier.ReduceAsync(document).Result + + Dim expected As String = Imports System.Diagnostics +Imports System +Imports System.IO + +Namespace NS + + Public Class C + + End Class +End Namespace + +Module Program + + Public Sub Main() + Dim i As Integer = 0 + Console.WriteLine(i.ToString()) + Dim p As Process = Process.GetCurrentProcess() + Console.WriteLine(p.Id) + End Sub +End Module +.Value + Dim actual As String = document.GetSyntaxRootAsync().Result.ToString().Replace(vbCrLf, vbLf) + Assert.Equal(expected, actual) + End Sub + +#End Region + + End Class +End Namespace diff --git a/samples/VisualBasic/APISamples/Parsing.vb b/samples/VisualBasic/APISamples/Parsing.vb new file mode 100644 index 000000000..c5a44aac8 --- /dev/null +++ b/samples/VisualBasic/APISamples/Parsing.vb @@ -0,0 +1,118 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Xunit + +Public Class Parsing + + + Sub TextParseTreeRoundtrip() + Dim code = + +Class C + Sub M() + End Sub +End Class ' exact text round trip, including comments and whitespace +.GetCode() + + Dim tree = SyntaxFactory.ParseSyntaxTree(code) + Assert.Equal(code, tree.GetText().ToString()) + End Sub + + + Sub DetermineValidIdentifierName() + ValidIdentifier("[Class]", True) + ValidIdentifier("Class", False) + End Sub + + Sub ValidIdentifier(identifier As String, expectedValid As Boolean) + Dim token = SyntaxFactory.ParseToken(identifier) + Assert.Equal(expectedValid, token.Kind() = SyntaxKind.IdentifierToken AndAlso token.Span.Length = identifier.Length) + End Sub + + + Sub SyntaxFactsMethods() + Assert.Equal("Protected Friend", SyntaxFacts.GetText(Accessibility.ProtectedOrFriend)) + Assert.Equal("Private Protected", SyntaxFacts.GetText(Accessibility.ProtectedAndFriend)) + Assert.Equal("Me", SyntaxFacts.GetText(SyntaxKind.MeKeyword)) + Assert.Equal(SyntaxKind.CharacterLiteralExpression, SyntaxFacts.GetLiteralExpression(SyntaxKind.CharacterLiteralToken)) + Assert.Equal(False, SyntaxFacts.IsPunctuation(SyntaxKind.StringLiteralToken)) + End Sub + + + Sub ParseTokens() + Dim tokens = SyntaxFactory.ParseTokens("Class C ' trivia") + Dim fullTexts = tokens.Select(Function(token) token.ToFullString()) + Assert.True(fullTexts.SequenceEqual({"Class ", "C ' trivia", ""})) + End Sub + + + Sub ParseExpression() + Dim expression = SyntaxFactory.ParseExpression("1 + 2") + If expression.Kind() = SyntaxKind.AddExpression Then + Dim binaryExpression = CType(expression, BinaryExpressionSyntax) + Dim operatorToken = binaryExpression.OperatorToken + Assert.Equal("+", operatorToken.ToString()) + Dim left = binaryExpression.Left + Assert.Equal(SyntaxKind.NumericLiteralExpression, left.Kind) + End If + End Sub + + + Sub IncrementalParse() + Dim oldCode = + +Class C +End Class +.GetCode() + Dim newCode = + + Sub M() + End Sub +.GetCode() + + Dim oldText = SourceText.From(oldCode) + Dim newText = oldText.WithChanges(New TextChange(New TextSpan(oldCode.IndexOf("End"), 0), newCode)) + + Dim oldTree = SyntaxFactory.ParseSyntaxTree(oldText) + Dim newTree = oldTree.WithChangedText(newText) + + Assert.Equal(newText.ToString(), newTree.ToString()) + End Sub + + + Sub PreprocessorDirectives() + Dim code = + +#If True +Class A +End Class +#Else +Class B +End Class +#End If +.GetCode() + + Dim tree = SyntaxFactory.ParseSyntaxTree(code) + + Dim eof = tree.GetRoot().FindToken(tree.GetText().Length, False) + Assert.Equal(True, eof.HasLeadingTrivia) + Assert.Equal(False, eof.HasTrailingTrivia) + Assert.Equal(True, eof.ContainsDirectives) + + Dim trivia = eof.LeadingTrivia + Assert.Equal(3, trivia.Count) + Assert.Equal("#Else" & vbCrLf, trivia.ElementAt(0).ToFullString()) + Assert.Equal(SyntaxKind.DisabledTextTrivia, trivia.ElementAt(1).Kind) + Assert.Equal("#End If", trivia.ElementAt(2).ToString()) + + Dim directive = tree.GetRoot().GetLastDirective() + Assert.Equal("#End If", directive.ToString()) + + directive = directive.GetPreviousDirective() + Assert.Equal("#Else" & vbCrLf, directive.ToFullString()) + End Sub +End Class diff --git a/samples/VisualBasic/APISamples/SymbolsAndSemantics.vb b/samples/VisualBasic/APISamples/SymbolsAndSemantics.vb new file mode 100644 index 000000000..63ca1859e --- /dev/null +++ b/samples/VisualBasic/APISamples/SymbolsAndSemantics.vb @@ -0,0 +1,324 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Text +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Xunit + +Public Class SymbolsAndSemantics + + + Public Sub GetExpressionType() + Dim code = + +Class C + Public Shared Sub Method() + Dim local As String = New C().ToString() & String.Empty + End Sub +End Class +.GetCode() + + Dim testCode = New TestCodeContainer(code) + + Dim localDeclaration = testCode.SyntaxTree.GetRoot().DescendantNodes().OfType(Of LocalDeclarationStatementSyntax).First() + Dim initializer = localDeclaration.Declarators.First().Initializer.Value + Dim semanticInfo = testCode.SemanticModel.GetTypeInfo(initializer) + Assert.Equal("String", semanticInfo.Type.Name) + End Sub + + + Public Sub BindNameToSymbol() + Dim code = New TestCodeContainer("Imports System") + Dim compilationUnit = CType(code.SyntaxTree.GetRoot(), CompilationUnitSyntax) + + Dim name = CType(compilationUnit.Imports(0).ImportsClauses.First(), SimpleImportsClauseSyntax).Name + Assert.Equal("System", name.ToString()) + + Dim nameInfo = code.SemanticModel.GetSymbolInfo(name) + Dim nameSymbol = CType(nameInfo.Symbol, INamespaceSymbol) + Assert.True(nameSymbol.GetNamespaceMembers().Any(Function(s) s.Name = "Collections")) + End Sub + + + Public Sub GetDeclaredSymbol() + Dim code = + +Namespace Acme + Friend Class C$lass1 + End Class +End Namespace +.GetCode() + + Dim testCode = New TestCodeContainer(code) + Dim symbol = testCode.SemanticModel.GetDeclaredSymbol(CType(testCode.SyntaxNode, TypeStatementSyntax)) + + Assert.Equal(True, symbol.CanBeReferencedByName) + Assert.Equal("Acme", symbol.ContainingNamespace.Name) + Assert.Equal(Accessibility.Friend, symbol.DeclaredAccessibility) + Assert.Equal(SymbolKind.NamedType, symbol.Kind) + Assert.Equal("Class1", symbol.Name) + Assert.Equal("Acme.Class1", symbol.ToDisplayString()) + Assert.Equal("Acme.Class1", symbol.ToString()) + End Sub + + + Public Sub GetSymbolXmlDocComments() + Dim code = + +''' <summary> +''' This is a test class! +''' </summary> +Class C$lass1 +End Class +.GetCode() + + Dim testCode = New TestCodeContainer(code) + + Dim symbol = testCode.SemanticModel.GetDeclaredSymbol(CType(testCode.SyntaxNode, TypeStatementSyntax)) + Dim actualXml = symbol.GetDocumentationCommentXml() + Dim expectedXml = " This is a test class! " + Assert.Equal(expectedXml, actualXml.Replace(vbCr, "").Replace(vbLf, "")) + End Sub + + + Public Sub SymbolDisplayFormatTest() + Dim code = + +Class C1(Of T) +End Class +Class C2 + Public Shared Function M(Of TSource)(source as C1(Of TSource), index as Integer) As TSource + End Function +End Class +.GetCode() + + Dim testCode = New TestCodeContainer(code) + + Dim displayFormat = New SymbolDisplayFormat( + genericsOptions:= + SymbolDisplayGenericsOptions.IncludeTypeParameters Or + SymbolDisplayGenericsOptions.IncludeVariance, + memberOptions:= + SymbolDisplayMemberOptions.IncludeParameters Or + SymbolDisplayMemberOptions.IncludeModifiers Or + SymbolDisplayMemberOptions.IncludeAccessibility Or + SymbolDisplayMemberOptions.IncludeType Or + SymbolDisplayMemberOptions.IncludeContainingType, + kindOptions:= + SymbolDisplayKindOptions.IncludeMemberKeyword, + parameterOptions:= + SymbolDisplayParameterOptions.IncludeExtensionThis Or + SymbolDisplayParameterOptions.IncludeType Or + SymbolDisplayParameterOptions.IncludeName Or + SymbolDisplayParameterOptions.IncludeDefaultValue, + miscellaneousOptions:= + SymbolDisplayMiscellaneousOptions.UseSpecialTypes) + + Dim symbol = testCode.Compilation.SourceModule.GlobalNamespace.GetTypeMembers("C2").First().GetMembers("M").First() + Assert.Equal( + "Public Shared Function C2.M(Of TSource)(source As C1(Of TSource), index As Integer) As TSource", + symbol.ToDisplayString(displayFormat)) + End Sub + + + Public Sub EnumerateSymbolsInCompilation() + Dim file1 = + +Public Class Animal + Public Overridable Sub MakeSound() + End Sub +End Class +.GetCode() + + Dim file2 = + +Class Cat + Inherits Animal + + Public Overrides Sub MakeSound() + End Sub +End Class +.GetCode() + + + Dim comp = VisualBasicCompilation.Create( + "test", + syntaxTrees:={SyntaxFactory.ParseSyntaxTree(file1), SyntaxFactory.ParseSyntaxTree(file2)}, + references:={MetadataReference.CreateFromFile(GetType(Object).Assembly.Location)}) + + Dim globalNamespace = comp.SourceModule.GlobalNamespace + + Dim builder = New StringBuilder() + EnumSymbols(globalNamespace, builder) + + Dim expected = "Global" & vbCrLf & + "Animal" & vbCrLf & + "Public Sub New()" & vbCrLf & + "Public Overridable Sub MakeSound()" & vbCrLf & + "Cat" & vbCrLf & + "Public Sub New()" & vbCrLf & + "Public Overrides Sub MakeSound()" & vbCrLf + + Assert.Equal(expected, builder.ToString()) + End Sub + + Private Sub EnumSymbols(symbol As ISymbol, builder As StringBuilder) + builder.AppendLine(symbol.ToString()) + + For Each childSymbol In GetMembers(symbol) + EnumSymbols(childSymbol, builder) + Next + End Sub + + Private Function GetMembers(parent As ISymbol) As IEnumerable(Of ISymbol) + Dim container = TryCast(parent, INamespaceOrTypeSymbol) + If container IsNot Nothing Then + Return container.GetMembers().AsEnumerable() + End If + + Return Enumerable.Empty(Of ISymbol)() + End Function + + + Public Sub AnalyzeRegionControlFlow() + Dim code = + +Class C + Public Sub F() + Goto L1 ' 1 + +'start + L1: Stop + + If False Then + Return + End If +'end + Goto L1 ' 2 + End Sub +End Class +.GetCode() + + Dim testCode = New TestCodeContainer(code) + + Dim firstStatement As StatementSyntax = Nothing + Dim lastStatement As StatementSyntax = Nothing + testCode.GetStatementsBetweenMarkers(firstStatement, lastStatement) + + Dim controlFlowAnalysis1 = testCode.SemanticModel.AnalyzeControlFlow(firstStatement, lastStatement) + Assert.Equal(1, controlFlowAnalysis1.EntryPoints.Count()) + Assert.Equal(1, controlFlowAnalysis1.ExitPoints.Count()) + Assert.True(controlFlowAnalysis1.EndPointIsReachable) + + Dim methodBlock = testCode.SyntaxTree.GetRoot().DescendantNodes().OfType(Of MethodBlockSyntax)().First() + Dim controlFlowAnalysis2 = testCode.SemanticModel.AnalyzeControlFlow(methodBlock.Statements.First, methodBlock.Statements.Last) + Assert.False(controlFlowAnalysis2.EndPointIsReachable) + End Sub + + + Public Sub AnalyzeRegionDataFlow() + Dim code = + +Class C + Public Sub F(x As Integer) + Dim a As Integer +'start + Dim b As Integer + Dim x As Integer + Dim y As Integer = 1 + + If True Then + Dim z As String = "a" + End If +'end + Dim c As Integer + End Sub +End Class +.GetCode() + + Dim testCode = New TestCodeContainer(code) + + Dim firstStatement As StatementSyntax = Nothing + Dim lastStatement As StatementSyntax = Nothing + testCode.GetStatementsBetweenMarkers(firstStatement, lastStatement) + + Dim dataFlowAnalysis = testCode.SemanticModel.AnalyzeDataFlow(firstStatement, lastStatement) + Assert.Equal("b,x,y,z", String.Join(",", dataFlowAnalysis.VariablesDeclared.Select(Function(s) s.Name))) + End Sub + + + Public Sub SemanticFactsTests() + Dim code = + +Class C1 + Sub M(i As Integer) + End Sub +End Class +Class C2 + Sub M(i As Integer) + End Sub +End Class +.GetCode() + + Dim testCode = New TestCodeContainer(code) + + Dim classNode1 = CType(testCode.SyntaxTree.GetRoot().FindToken(testCode.Text.IndexOf("C1")).Parent, ClassStatementSyntax) + Dim classNode2 = CType(testCode.SyntaxTree.GetRoot().FindToken(testCode.Text.IndexOf("C2")).Parent, ClassStatementSyntax) + + Dim class1 = testCode.SemanticModel.GetDeclaredSymbol(classNode1) + Dim class2 = testCode.SemanticModel.GetDeclaredSymbol(classNode2) + + Dim method1 = CType(class1.GetMembers().First(), IMethodSymbol) + Dim method2 = CType(class2.GetMembers().First(), IMethodSymbol) + + ' TODO: this API has been made internal. What is the replacement? Do we even need it here? + ' Assert.IsTrue(Symbol.HaveSameSignature(method1, method2)) + End Sub + + + Public Sub FailedOverloadResolution() + Dim code = + +Option Strict On +Module Module1 + Sub Main() + X$.F("hello") + End Sub +End Module +Module X + Sub F() + End Sub + Sub F(i As Integer) + End Sub +End Module +.GetCode() + + Dim testCode = New TestCodeContainer(code) + + Dim expression = CType(testCode.SyntaxNode, ExpressionSyntax) + Dim typeInfo = testCode.SemanticModel.GetTypeInfo(expression) + Dim semanticInfo = testCode.SemanticModel.GetSymbolInfo(expression) + + Assert.Null(typeInfo.Type) + Assert.Null(typeInfo.ConvertedType) + Assert.Null(semanticInfo.Symbol) + Assert.Equal(CandidateReason.OverloadResolutionFailure, semanticInfo.CandidateReason) + Assert.Equal(1, semanticInfo.CandidateSymbols.Count) + + Assert.Equal("Public Sub F(i As Integer)", semanticInfo.CandidateSymbols(0).ToDisplayString()) + Assert.Equal(SymbolKind.Method, semanticInfo.CandidateSymbols(0).Kind) + + Dim memberGroup = testCode.SemanticModel.GetMemberGroup(expression) + + Assert.Equal(2, memberGroup.Count) + + Dim sortedMethodGroup = Aggregate s In memberGroup.AsEnumerable() + Order By s.ToDisplayString() + Into ToArray() + + Assert.Equal("Public Sub F()", sortedMethodGroup(0).ToDisplayString()) + Assert.Equal("Public Sub F(i As Integer)", sortedMethodGroup(1).ToDisplayString()) + End Sub +End Class diff --git a/samples/VisualBasic/APISamples/SyntaxTrees.vb b/samples/VisualBasic/APISamples/SyntaxTrees.vb new file mode 100644 index 000000000..d7ef25b94 --- /dev/null +++ b/samples/VisualBasic/APISamples/SyntaxTrees.vb @@ -0,0 +1,179 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Text +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Xunit + +Public Class SyntaxTrees + + + Public Sub FindNodeUsingMembers() + Dim code = + +Class C + Sub M(i As Integer) + End Sub +End Class +.GetCode() + + Dim tree = SyntaxFactory.ParseSyntaxTree(code) + Dim compilationUnit = CType(tree.GetRoot(), CompilationUnitSyntax) + Dim typeBlock = CType(compilationUnit.Members(0), TypeBlockSyntax) + Dim methodBlock = CType(typeBlock.Members(0), MethodBlockSyntax) + Dim parameter = methodBlock.SubOrFunctionStatement.ParameterList.Parameters(0) + Dim parameterName = parameter.Identifier.Identifier + Assert.Equal("i", parameterName.ValueText) + End Sub + + + Public Sub FindNodeUsingQuery() + Dim code = + +Class C + Sub M(i As Integer) + End Sub +End Class +.GetCode() + + Dim root As SyntaxNode = SyntaxFactory.ParseCompilationUnit(code) + Dim parameter = root.DescendantNodes().OfType(Of ParameterSyntax)().First() + Assert.Equal("i", parameter.Identifier.Identifier.ValueText) + End Sub + + + Public Sub UpdateNode() + Dim code = + +Class C + Sub M() + End Sub +End Class +.GetCode() + + Dim tree = SyntaxFactory.ParseSyntaxTree(code) + Dim root = CType(tree.GetRoot(), CompilationUnitSyntax) + Dim method = CType(root.DescendantNodes().OfType(Of MethodBlockSyntax)().First().SubOrFunctionStatement, MethodStatementSyntax) + + Dim newMethod = method.Update( + method.Kind, + method.AttributeLists, + method.Modifiers, + method.SubOrFunctionKeyword, + SyntaxFactory.Identifier("NewMethodName"), + method.TypeParameterList, + method.ParameterList, + method.AsClause, + method.HandlesClause, + method.ImplementsClause) + + Dim newRoot = root.ReplaceNode(method, newMethod) + Dim newTree = tree.WithRootAndOptions(newRoot, tree.Options) + + Dim newCode = + +Class C + Sub NewMethodName() + End Sub +End Class +.GetCode() + + Assert.Equal(newCode, newTree.GetText().ToString()) + End Sub + + Private Class FileContentsDumper + Inherits VisualBasicSyntaxWalker + + Private ReadOnly sb As New StringBuilder() + + Public Overrides Sub VisitClassStatement(node As ClassStatementSyntax) + sb.AppendLine(node.ClassKeyword.ValueText & " " & node.Identifier.ValueText) + MyBase.VisitClassStatement(node) + End Sub + + Public Overrides Sub VisitStructureStatement(node As StructureStatementSyntax) + sb.AppendLine(node.StructureKeyword.ValueText & " " & node.Identifier.ValueText) + MyBase.VisitStructureStatement(node) + End Sub + + Public Overrides Sub VisitInterfaceStatement(node As InterfaceStatementSyntax) + sb.AppendLine(node.InterfaceKeyword.ValueText & " " & node.Identifier.ValueText) + MyBase.VisitInterfaceStatement(node) + End Sub + + Public Overrides Sub VisitMethodStatement(node As MethodStatementSyntax) + sb.AppendLine(" " & node.Identifier.ToString()) + MyBase.VisitMethodStatement(node) + End Sub + + Public Overrides Function ToString() As String + Return sb.ToString() + End Function + End Class + + + Public Sub WalkTreeUsingSyntaxWalker() + Dim code = + +Class C + Sub M1() + End Sub + + Structure S + End Structure + + Sub M2() + End Sub +End Class +.GetCode() + + Dim node As SyntaxNode = SyntaxFactory.ParseCompilationUnit(code) + Dim visitor As FileContentsDumper = New FileContentsDumper() + visitor.Visit(node) + + Dim expectedText = "Class C" & vbCrLf & + " M1" & vbCrLf & + "Structure S" & vbCrLf & + " M2" & vbCrLf + + Assert.Equal(expectedText, visitor.ToString()) + End Sub + + Private Class RemoveMethodsRewriter + Inherits VisualBasicSyntaxRewriter + + Public Overrides Function VisitMethodBlock(node As MethodBlockSyntax) As SyntaxNode + ' Returning nothing removes the syntax node + Return Nothing + End Function + End Class + + + + Public Sub TransformTreeUsingSyntaxRewriter() + Dim code = + +Class C + Private field As Integer + + Sub M() + End Sub +End Class +.GetCode() + + Dim tree = SyntaxFactory.ParseSyntaxTree(code) + Dim root = tree.GetRoot() + Dim newRoot = root.RemoveNodes(root.DescendantNodes.OfType(Of MethodBlockSyntax), SyntaxRemoveOptions.KeepNoTrivia) + + Dim expectedCode = + +Class C + Private field As Integer +End Class +.GetCode() + + Assert.Equal(expectedCode, newRoot.ToFullString()) + End Sub + +End Class diff --git a/samples/VisualBasic/APISamples/TestCodeContainer.vb b/samples/VisualBasic/APISamples/TestCodeContainer.vb new file mode 100644 index 000000000..dda24d350 --- /dev/null +++ b/samples/VisualBasic/APISamples/TestCodeContainer.vb @@ -0,0 +1,61 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +''' Helper class to bundle together information about a piece of analyzed test code. +Class TestCodeContainer + + Public Property Position As Integer + + Public Property Text As String + + Public Property SyntaxTree As SyntaxTree + + Public Property Token As SyntaxToken + + Public Property SyntaxNode As SyntaxNode + + Public Property Compilation As Compilation + + Public Property SemanticModel As SemanticModel + + Public Sub New(textWithMarker As String) + Position = textWithMarker.IndexOf("$"c) + If Position <> -1 Then + textWithMarker = textWithMarker.Remove(Position, 1) + End If + + Text = textWithMarker + SyntaxTree = SyntaxFactory.ParseSyntaxTree(Text) + If Position <> -1 Then + Token = SyntaxTree.GetRoot().FindToken(Position) + SyntaxNode = Token.Parent + End If + + ' Use the mscorlib from our current process + Compilation = VisualBasicCompilation.Create( + "test", + syntaxTrees:={SyntaxTree}, + references:={MetadataReference.CreateFromFile(GetType(Object).Assembly.Location)}) + + SemanticModel = Compilation.GetSemanticModel(SyntaxTree) + End Sub + + Public Sub GetStatementsBetweenMarkers(ByRef firstStatement As StatementSyntax, ByRef lastStatement As StatementSyntax) + Dim span As TextSpan = GetSpanBetweenMarkers() + Dim statementsInside = SyntaxTree.GetRoot().DescendantNodes(span).OfType(Of StatementSyntax).Where(Function(s) span.Contains(s.Span)) + Dim first = statementsInside.First() + firstStatement = first + lastStatement = statementsInside.Where(Function(s) s.Parent Is first.Parent).Last() + End Sub + + Public Function GetSpanBetweenMarkers() As TextSpan + Dim startComment = SyntaxTree.GetRoot().DescendantTrivia().First(Function(t) t.ToString().Contains("start")) + Dim endComment = SyntaxTree.GetRoot().DescendantTrivia().First(Function(t) t.ToString().Contains("end")) + Dim span = TextSpan.FromBounds(startComment.FullSpan.End, endComment.FullSpan.Start) + Return span + End Function +End Class diff --git a/samples/VisualBasic/Analyzers/Analyzers.Implementation/AdditionalFileAnalyzers/SimpleAdditionalFileAnalyzer.vb b/samples/VisualBasic/Analyzers/Analyzers.Implementation/AdditionalFileAnalyzers/SimpleAdditionalFileAnalyzer.vb new file mode 100644 index 000000000..1a90405f6 --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Implementation/AdditionalFileAnalyzers/SimpleAdditionalFileAnalyzer.vb @@ -0,0 +1,77 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports System.IO +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Text + +Namespace BasicAnalyzers + + ''' + ''' Analyzer to demonstrate reading an additional file line-by-line. + ''' It looks for an additional file named "Terms.txt" and extracts a set of + ''' terms, one per line. It then detects type names that use those terms and + ''' reports diagnostics. + ''' + + Public Class SimpleAdditionalFileAnalyzer + Inherits DiagnosticAnalyzer + + Private Const Title As String = "Type name contains invalid term" + Private Const MessageFormat As String = "The term '{0}' is not allowed in a type name." + + Private Shared ReadOnly Rule As DiagnosticDescriptor = + New DiagnosticDescriptor( + DiagnosticIds.SimpleAdditionalFileAnalyzerRuleId, + Title, + MessageFormat, + DiagnosticCategories.AdditionalFile, + DiagnosticSeverity.Error, + isEnabledByDefault:=True) + + Public Overrides ReadOnly Property SupportedDiagnostics As ImmutableArray(Of DiagnosticDescriptor) + Get + Return ImmutableArray.Create(Rule) + End Get + End Property + + Public Overrides Sub Initialize(context As AnalysisContext) + context.RegisterCompilationStartAction( + Sub(compilationStartContext) + ' Find the additional file with the terms. + Dim additionalFiles As ImmutableArray(Of AdditionalText) = compilationStartContext.Options.AdditionalFiles + Dim termsFile As AdditionalText = additionalFiles.FirstOrDefault( + Function(file) + Return Path.GetFileName(file.Path).Equals("Terms.txt") + End Function) + + If termsFile IsNot Nothing Then + Dim terms As HashSet(Of String) = New HashSet(Of String) + + ' Read the file line-by-line to get the terms. + Dim fileText As SourceText = termsFile.GetText(compilationStartContext.CancellationToken) + For Each line As TextLine In fileText.Lines + terms.Add(line.ToString()) + Next + + ' Check every named type for the invalid terms. + compilationStartContext.RegisterSymbolAction( + Sub(symbolAnalysisContext) + Dim namedTypeSymbol As INamedTypeSymbol = DirectCast(symbolAnalysisContext.Symbol, INamedTypeSymbol) + Dim symbolName As String = namedTypeSymbol.Name + + For Each term As String In terms + If symbolName.Contains(term) Then + symbolAnalysisContext.ReportDiagnostic( + Diagnostic.Create(Rule, namedTypeSymbol.Locations(0), term)) + End If + Next + End Sub, + SymbolKind.NamedType) + End If + + End Sub) + End Sub + End Class +End Namespace diff --git a/samples/VisualBasic/Analyzers/Analyzers.Implementation/AdditionalFileAnalyzers/XmlAdditionalFileAnalyzer.vb b/samples/VisualBasic/Analyzers/Analyzers.Implementation/AdditionalFileAnalyzers/XmlAdditionalFileAnalyzer.vb new file mode 100644 index 000000000..aa5257b4d --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Implementation/AdditionalFileAnalyzers/XmlAdditionalFileAnalyzer.vb @@ -0,0 +1,85 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports System.IO +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Text + +Namespace BasicAnalyzers + + ''' + ''' Analyzer to demonstrate reading an additional file with a structed format. + ''' It looks for an additional file named "Terms.xml" and dumps it to a stream + ''' so that it can be loaded into an . It then extracts + ''' terms from the XML, detects type names that use those terms and reports + ''' diagnostics on them. + ''' + + Public Class XmlAdditionalFileAnalyzer + Inherits DiagnosticAnalyzer + + Private Const Title As String = "Type name contains invalid term" + Private Const MessageFormat As String = "The term '{0}' is not allowed in a type name." + + Private Shared ReadOnly Rule As DiagnosticDescriptor = + New DiagnosticDescriptor( + DiagnosticIds.XmlAdditionalFileAnalyzerRuleId, + Title, + MessageFormat, + DiagnosticCategories.AdditionalFile, + DiagnosticSeverity.Error, + isEnabledByDefault:=True) + + Public Overrides ReadOnly Property SupportedDiagnostics As ImmutableArray(Of DiagnosticDescriptor) + Get + Return ImmutableArray.Create(Rule) + End Get + End Property + + Public Overrides Sub Initialize(context As AnalysisContext) + context.RegisterCompilationStartAction( + Sub(compilationStartContext) + ' Find the additional file with the terms. + Dim additionalFiles As ImmutableArray(Of AdditionalText) = compilationStartContext.Options.AdditionalFiles + Dim termsFile As AdditionalText = additionalFiles.FirstOrDefault( + Function(file) + Return Path.GetFileName(file.Path).Equals("Terms.txt") + End Function) + + If termsFile IsNot Nothing Then + Dim terms As HashSet(Of String) = New HashSet(Of String) + Dim fileText As SourceText = termsFile.GetText(compilationStartContext.CancellationToken) + + ' Write the additional file back to a stream. + Dim stream As MemoryStream = New MemoryStream() + Using writer As StreamWriter = New StreamWriter(stream) + fileText.Write(writer) + End Using + + ' Read all the elements to get the terms. + Dim document As XDocument = XDocument.Load(stream) + For Each termElement As XElement In document.Descendants("Term") + terms.Add(termElement.Value) + Next + + ' Check every named type for the invalid terms. + compilationStartContext.RegisterSymbolAction( + Sub(symbolAnalysisContext) + Dim namedTypeSymbol As INamedTypeSymbol = DirectCast(symbolAnalysisContext.Symbol, INamedTypeSymbol) + Dim symbolName As String = namedTypeSymbol.Name + + For Each term As String In terms + If symbolName.Contains(term) Then + symbolAnalysisContext.ReportDiagnostic( + Diagnostic.Create(Rule, namedTypeSymbol.Locations(0), term)) + End If + Next + End Sub, + SymbolKind.NamedType) + End If + + End Sub) + End Sub + End Class +End Namespace diff --git a/samples/VisualBasic/Analyzers/Analyzers.Implementation/Analyzers.VisualBasic.vbproj b/samples/VisualBasic/Analyzers/Analyzers.Implementation/Analyzers.VisualBasic.vbproj new file mode 100644 index 000000000..5a4aa37a7 --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Implementation/Analyzers.VisualBasic.vbproj @@ -0,0 +1,39 @@ + + + + netstandard1.3 + false + True + + + + Roslyn.VB.Sample.Analyzers + 1.0.0.0 + Microsoft + http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE + http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE + http://ICON_URL_HERE_OR_DELETE_THIS_LINE + http://REPOSITORY_URL_HERE_OR_DELETE_THIS_LINE + false + Samples.Analyzers + Summary of changes made in this release of the package. + Copyright + Samples.Analyzers, analyzers + true + + + + + + + + + + + + + + + + + diff --git a/samples/VisualBasic/Analyzers/Analyzers.Implementation/DiagnosticCategories.vb b/samples/VisualBasic/Analyzers/Analyzers.Implementation/DiagnosticCategories.vb new file mode 100644 index 000000000..77d042e1a --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Implementation/DiagnosticCategories.vb @@ -0,0 +1,9 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Namespace BasicAnalyzers + Public Module DiagnosticCategories + Public Const Stateless As String = "SampleStatelessAnalyzers" + Public Const Stateful As String = "SampleStatefulAnalyzers" + Public Const AdditionalFile As String = "SampleAdditionalFileAnalyzers" + End Module +End Namespace diff --git a/samples/VisualBasic/Analyzers/Analyzers.Implementation/DiagnosticIds.vb b/samples/VisualBasic/Analyzers/Analyzers.Implementation/DiagnosticIds.vb new file mode 100644 index 000000000..e49c77ef9 --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Implementation/DiagnosticIds.vb @@ -0,0 +1,22 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Namespace BasicAnalyzers + Public Module DiagnosticIds + ' Stateless analyzer IDs. + Public Const SymbolAnalyzerRuleId As String = "VBS0001" + Public Const SyntaxNodeAnalyzerRuleId As String = "VBS0002" + Public Const SyntaxTreeAnalyzerRuleId As String = "VBS0003" + Public Const SemanticModelAnalyzerRuleId As String = "VBS0004" + Public Const CodeBlockAnalyzerRuleId As String = "VBS0005" + Public Const CompilationAnalyzerRuleId As String = "VBS0006" + + ' Stateful analyzer IDs. + Public Const CodeBlockStartedAnalyzerRuleId As String = "VBS0101" + Public Const CompilationStartedAnalyzerRuleId As String = "VBS0102" + Public Const CompilationStartedAnalyzerWithCompilationWideAnalysisRuleId As String = "VBS0103" + + ' Additional File analyzer IDs. + Public Const SimpleAdditionalFileAnalyzerRuleId = "VBS0201" + Public Const XmlAdditionalFileAnalyzerRuleId = "VBS0202" + End Module +End Namespace diff --git a/samples/VisualBasic/Analyzers/Analyzers.Implementation/My Project/Resources.Designer.vb b/samples/VisualBasic/Analyzers/Analyzers.Implementation/My Project/Resources.Designer.vb new file mode 100644 index 000000000..ca596e5be --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Implementation/My Project/Resources.Designer.vb @@ -0,0 +1,307 @@ +'------------------------------------------------------------------------------ +' +' This code was generated by a tool. +' Runtime Version:4.0.30319.42000 +' +' Changes to this file may cause incorrect behavior and will be lost if +' the code is regenerated. +' +'------------------------------------------------------------------------------ + +Option Strict On +Option Explicit On + +Imports System +Imports System.Reflection + +Namespace My.Resources + + 'This class was auto-generated by the StronglyTypedResourceBuilder + 'class via a tool like ResGen or Visual Studio. + 'To add or remove a member, edit your .ResX file then rerun ResGen + 'with the /str option, or rebuild your VS project. + ''' + ''' A strongly-typed resource class, for looking up localized strings, etc. + ''' + _ + Public Module Resources + + Private resourceMan As Global.System.Resources.ResourceManager + + Private resourceCulture As Global.System.Globalization.CultureInfo + + ''' + ''' Returns the cached ResourceManager instance used by this class. + ''' + _ + Public ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager + Get + If Object.ReferenceEquals(resourceMan, Nothing) Then + Dim temp As Global.System.Resources.ResourceManager = New Global.System.Resources.ResourceManager("Resources", GetType(Resources).GetTypeInfo.Assembly) + resourceMan = temp + End If + Return resourceMan + End Get + End Property + + ''' + ''' Overrides the current thread's CurrentUICulture property for all + ''' resource lookups using this strongly typed resource class. + ''' + _ + Public Property Culture() As Global.System.Globalization.CultureInfo + Get + Return resourceCulture + End Get + Set + resourceCulture = value + End Set + End Property + + ''' + ''' Looks up a localized string similar to Remove unnecessary methods.. + ''' + Public ReadOnly Property CodeBlockAnalyzerDescription() As String + Get + Return ResourceManager.GetString("CodeBlockAnalyzerDescription", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Method '{0}' is a non-virtual method with an empty body. Consider removing this method from your assembly.. + ''' + Public ReadOnly Property CodeBlockAnalyzerMessageFormat() As String + Get + Return ResourceManager.GetString("CodeBlockAnalyzerMessageFormat", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Remove unnecessary methods. + ''' + Public ReadOnly Property CodeBlockAnalyzerTitle() As String + Get + Return ResourceManager.GetString("CodeBlockAnalyzerTitle", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Remove unused parameters.. + ''' + Public ReadOnly Property CodeBlockStartedAnalyzerDescription() As String + Get + Return ResourceManager.GetString("CodeBlockStartedAnalyzerDescription", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Parameter '{0}' is unused in the method '{1}'.. + ''' + Public ReadOnly Property CodeBlockStartedAnalyzerMessageFormat() As String + Get + Return ResourceManager.GetString("CodeBlockStartedAnalyzerMessageFormat", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Remove unused parameters. + ''' + Public ReadOnly Property CodeBlockStartedAnalyzerTitle() As String + Get + Return ResourceManager.GetString("CodeBlockStartedAnalyzerTitle", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Dont suppress analyzer diagnostics.. + ''' + Public ReadOnly Property CompilationAnalyzerDescription() As String + Get + Return ResourceManager.GetString("CompilationAnalyzerDescription", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Analyzer diagnostic '{0}' is suppressed, consider removing this compilation wide suppression.. + ''' + Public ReadOnly Property CompilationAnalyzerMessageFormat() As String + Get + Return ResourceManager.GetString("CompilationAnalyzerMessageFormat", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Dont suppress analyzer diagnostics. + ''' + Public ReadOnly Property CompilationAnalyzerTitle() As String + Get + Return ResourceManager.GetString("CompilationAnalyzerTitle", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Do not implement unsupported interface.. + ''' + Public ReadOnly Property CompilationStartedAnalyzerDescription() As String + Get + Return ResourceManager.GetString("CompilationStartedAnalyzerDescription", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Type '{0}' implements interface '{1}', which is only meant for internal implementation and might change in future. You should avoid implementing this interface.. + ''' + Public ReadOnly Property CompilationStartedAnalyzerMessageFormat() As String + Get + Return ResourceManager.GetString("CompilationStartedAnalyzerMessageFormat", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Do not implement unsupported interface. + ''' + Public ReadOnly Property CompilationStartedAnalyzerTitle() As String + Get + Return ResourceManager.GetString("CompilationStartedAnalyzerTitle", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Secure types must not implement interfaces with unsecure methods.. + ''' + Public ReadOnly Property CompilationStartedAnalyzerWithCompilationWideAnalysisDescription() As String + Get + Return ResourceManager.GetString("CompilationStartedAnalyzerWithCompilationWideAnalysisDescription", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Type '{0}' is a secure type as it implements interface '{1}', but it also implements interface '{2}' which has unsecure method(s).. + ''' + Public ReadOnly Property CompilationStartedAnalyzerWithCompilationWideAnalysisMessageFormat() As String + Get + Return ResourceManager.GetString("CompilationStartedAnalyzerWithCompilationWideAnalysisMessageFormat", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Secure types must not implement interfaces with unsecure methods. + ''' + Public ReadOnly Property CompilationStartedAnalyzerWithCompilationWideAnalysisTitle() As String + Get + Return ResourceManager.GetString("CompilationStartedAnalyzerWithCompilationWideAnalysisTitle", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Source file declaration diagnostic count.. + ''' + Public ReadOnly Property SemanticModelAnalyzerDescription() As String + Get + Return ResourceManager.GetString("SemanticModelAnalyzerDescription", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Source file '{0}' has '{1}' declaration diagnostic(s). + ''' + Public ReadOnly Property SemanticModelAnalyzerMessageFormat() As String + Get + Return ResourceManager.GetString("SemanticModelAnalyzerMessageFormat", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Source file declaration diagnostics count. + ''' + Public ReadOnly Property SemanticModelAnalyzerTitle() As String + Get + Return ResourceManager.GetString("SemanticModelAnalyzerTitle", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Do not declare members with same name as containing type.. + ''' + Public ReadOnly Property SymbolAnalyzerDescription() As String + Get + Return ResourceManager.GetString("SymbolAnalyzerDescription", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Type '{0}' has one or more members with the same name, considering renaming the type or the members.. + ''' + Public ReadOnly Property SymbolAnalyzerMessageFormat() As String + Get + Return ResourceManager.GetString("SymbolAnalyzerMessageFormat", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Do not declare members with same name as containing type. + ''' + Public ReadOnly Property SymbolAnalyzerTitle() As String + Get + Return ResourceManager.GetString("SymbolAnalyzerTitle", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Declare explicit type for local declarations.. + ''' + Public ReadOnly Property SyntaxNodeAnalyzerDescription() As String + Get + Return ResourceManager.GetString("SyntaxNodeAnalyzerDescription", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Local '{0}' is implicitly typed. Consider specifying its type explicitly in the declaration.. + ''' + Public ReadOnly Property SyntaxNodeAnalyzerMessageFormat() As String + Get + Return ResourceManager.GetString("SyntaxNodeAnalyzerMessageFormat", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Declare explicit type for local declarations. + ''' + Public ReadOnly Property SyntaxNodeAnalyzerTitle() As String + Get + Return ResourceManager.GetString("SyntaxNodeAnalyzerTitle", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Do not suppress documentation comment diagnostics.. + ''' + Public ReadOnly Property SyntaxTreeAnalyzerDescription() As String + Get + Return ResourceManager.GetString("SyntaxTreeAnalyzerDescription", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Enable documentation comment diagnostics on source file '{0}'.. + ''' + Public ReadOnly Property SyntaxTreeAnalyzerMessageFormat() As String + Get + Return ResourceManager.GetString("SyntaxTreeAnalyzerMessageFormat", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Do not suppress documentation comment diagnostics. + ''' + Public ReadOnly Property SyntaxTreeAnalyzerTitle() As String + Get + Return ResourceManager.GetString("SyntaxTreeAnalyzerTitle", resourceCulture) + End Get + End Property + End Module +End Namespace diff --git a/samples/VisualBasic/Analyzers/Analyzers.Implementation/My Project/Resources.resx b/samples/VisualBasic/Analyzers/Analyzers.Implementation/My Project/Resources.resx new file mode 100644 index 000000000..29cc1e487 --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Implementation/My Project/Resources.resx @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Remove unnecessary methods. + An optional longer localizable description of the diagnostic. + + + Method '{0}' is a non-virtual method with an empty body. Consider removing this method from your assembly. + The format-able message the diagnostic displays. + + + Remove unnecessary methods + The title of the diagnostic. + + + Remove unused parameters. + An optional longer localizable description of the diagnostic. + + + Parameter '{0}' is unused in the method '{1}'. + The format-able message the diagnostic displays. + + + Remove unused parameters + The title of the diagnostic. + + + Do not suppress analyzer diagnostics. + An optional longer localizable description of the diagnostic. + + + Analyzer diagnostic '{0}' is suppressed, consider removing this compilation wide suppression. + The format-able message the diagnostic displays. + + + Dont suppress analyzer diagnostics + The title of the diagnostic. + + + Do not implement unsupported interface. + An optional longer localizable description of the diagnostic. + + + Type '{0}' implements interface '{1}', which is only meant for internal implementation and might change in future. You should avoid implementing this interface. + The format-able message the diagnostic displays. + + + Do not implement unsupported interface + The title of the diagnostic. + + + Secure types must not implement interfaces with unsecure methods. + An optional longer localizable description of the diagnostic. + + + Type '{0}' is a secure type as it implements interface '{1}', but it also implements interface '{2}' which has unsecure method(s). + The format-able message the diagnostic displays. + + + Secure types must not implement interfaces with unsecure methods + The title of the diagnostic. + + + Source file declaration diagnostic count. + An optional longer localizable description of the diagnostic. + + + Source file '{0}' has '{1}' declaration diagnostic(s) + The format-able message the diagnostic displays. + + + Source file declaration diagnostics count + The title of the diagnostic. + + + Do not declare members with same name as containing type. + An optional longer localizable description of the diagnostic. + + + Type '{0}' has one or more members with the same name, considering renaming the type or the members. + The format-able message the diagnostic displays. + + + Do not declare members with same name as containing type + The title of the diagnostic. + + + Declare explicit type for local declarations. + An optional longer localizable description of the diagnostic. + + + Local '{0}' is implicitly typed. Consider specifying its type explicitly in the declaration. + The format-able message the diagnostic displays. + + + Declare explicit type for local declarations + The title of the diagnostic. + + + Do not suppress documentation comment diagnostics. + An optional longer localizable description of the diagnostic. + + + Enable documentation comment diagnostics on source file '{0}'. + The format-able message the diagnostic displays. + + + Do not suppress documentation comment diagnostics + The title of the diagnostic. + + \ No newline at end of file diff --git a/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatefulAnalyzers/CodeBlockStartedAnalyzer.vb b/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatefulAnalyzers/CodeBlockStartedAnalyzer.vb new file mode 100644 index 000000000..2056b9842 --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatefulAnalyzers/CodeBlockStartedAnalyzer.vb @@ -0,0 +1,114 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace BasicAnalyzers + ''' + ''' Analyzer to demonstrate code block wide analysis. + ''' It computes and reports diagnostics for unused parameters in methods. + ''' It performs code block wide analysis to detect such unused parameters and reports diagnostics for them in the code block end action. + ''' + ''' The analyzer registers: + ''' (a) A code block start action, which initializes per-code block mutable state. We mark all parameters as unused at start of analysis. + ''' (b) A code block syntax node action, which identifes parameter references and marks the corresponding parameter as used. + ''' (c) A code block end action, which reports diagnostics based on the final state, for all parameters which are unused. + ''' + ''' + + Public Class CodeBlockStartedAnalyzer + Inherits DiagnosticAnalyzer + +#Region "Descriptor fields" + Friend Shared ReadOnly Title As LocalizableString = "Remove unused parameters" + Friend Shared ReadOnly MessageFormat As LocalizableString = "Parameter '{0}' is unused in the method '{1}'." + Friend Shared ReadOnly Description As LocalizableString = "Remove unused parameters." + + Friend Shared Rule As New DiagnosticDescriptor(DiagnosticIds.CodeBlockStartedAnalyzerRuleId, Title, MessageFormat, DiagnosticCategories.Stateful, DiagnosticSeverity.Warning, isEnabledByDefault:=True, description:=Description) +#End Region + + Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) + Get + Return ImmutableArray.Create(Rule) + End Get + End Property + + Public Overrides Sub Initialize(context As AnalysisContext) + context.RegisterCodeBlockStartAction(Of SyntaxKind)( + Sub(startCodeBlockContext) + ' We only care about method bodies. + If startCodeBlockContext.OwningSymbol.Kind <> SymbolKind.Method Then + Return + End If + + ' We only care about methods with parameters. + Dim method = DirectCast(startCodeBlockContext.OwningSymbol, IMethodSymbol) + If method.Parameters.IsEmpty Then + Return + End If + + ' Initialize local mutable state in the start action. + Dim analyzer = New UnusedParametersAnalyzer(method) + + ' Register an intermediate non-end action that accesses and modifies the state. + startCodeBlockContext.RegisterSyntaxNodeAction(AddressOf analyzer.AnalyzeSyntaxNode, SyntaxKind.IdentifierName) + + ' Register an end action to report diagnostics based on the final state. + startCodeBlockContext.RegisterCodeBlockEndAction(AddressOf analyzer.CodeBlockEndAction) + End Sub) + End Sub + + Private Class UnusedParametersAnalyzer + +#Region "Per-CodeBlock mutable state" + Private ReadOnly _unusedParameters As HashSet(Of IParameterSymbol) + Private ReadOnly _unusedParameterNames As HashSet(Of String) +#End Region + +#Region "State intialization" + Public Sub New(method As IMethodSymbol) + ' Initialization: Assume all parameters are unused. + Dim parameters = method.Parameters.Where(Function(p) Not p.IsImplicitlyDeclared AndAlso p.Locations.Length > 0) + _unusedParameters = New HashSet(Of IParameterSymbol)(parameters) + _unusedParameterNames = New HashSet(Of String)(parameters.Select(Function(p) p.Name)) + End Sub +#End Region + +#Region "Intermediate actions" + Public Sub AnalyzeSyntaxNode(context As SyntaxNodeAnalysisContext) + ' Check if we have any pending unreferenced parameters. + If _unusedParameters.Count = 0 Then + Return + End If + + ' Syntactic check to avoid invoking GetSymbolInfo for every identifier. + Dim identifier = DirectCast(context.Node, IdentifierNameSyntax) + If Not _unusedParameterNames.Contains(identifier.Identifier.ValueText) Then + Return + End If + + ' Mark parameter as used. + Dim parmeter = TryCast(context.SemanticModel.GetSymbolInfo(identifier, context.CancellationToken).Symbol, IParameterSymbol) + If parmeter IsNot Nothing AndAlso _unusedParameters.Contains(parmeter) Then + _unusedParameters.Remove(parmeter) + _unusedParameterNames.Remove(parmeter.Name) + End If + End Sub +#End Region + +#Region "End action" + Public Sub CodeBlockEndAction(context As CodeBlockAnalysisContext) + ' Report diagnostics for unused parameters. + For Each parameter In _unusedParameters + Dim diag = Diagnostic.Create(Rule, parameter.Locations(0), parameter.Name, parameter.ContainingSymbol.Name) + context.ReportDiagnostic(diag) + Next + End Sub +#End Region + + End Class + End Class +End Namespace diff --git a/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatefulAnalyzers/CompilationStartedAnalyzer.vb b/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatefulAnalyzers/CompilationStartedAnalyzer.vb new file mode 100644 index 000000000..3c4b2798c --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatefulAnalyzers/CompilationStartedAnalyzer.vb @@ -0,0 +1,68 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Diagnostics + +Namespace BasicAnalyzers + ''' + ''' Analyzer to demonstrate analysis within a compilation defining certain well-known symbol(s). + ''' It computes and reports diagnostics for all public implementations of an interface, which is only supposed to be implemented internally. + ''' + ''' The analyzer registers: + ''' (a) A compilation start action, which initializes per-compilation immutable state. We fetch and store the type symbol for the interface type in the compilation. + ''' (b) A compilation symbol action, which identifies all named types implementing this interface, and reports diagnostics for all but internal allowed well known types. + ''' + ''' + + Public Class CompilationStartedAnalyzer + Inherits DiagnosticAnalyzer + +#Region "Descriptor fields" + Friend Shared ReadOnly Title As LocalizableString = "Do not implement unsupported interface" + Friend Shared ReadOnly MessageFormat As LocalizableString = "Type '{0}' implements interface '{1}', which is only meant for internal implementation and might change in future. You should avoid implementing this interface." + Friend Shared ReadOnly Description As LocalizableString = "Do not implement unsupported interface." + + Friend Shared Rule As New DiagnosticDescriptor(DiagnosticIds.CompilationStartedAnalyzerRuleId, Title, MessageFormat, DiagnosticCategories.Stateful, DiagnosticSeverity.Warning, isEnabledByDefault:=True, description:=Description) +#End Region + + Public Const DontInheritInterfaceTypeName As String = "MyInterfaces.Interface" + Public Const AllowedInternalImplementationTypeName As String = "MyInterfaces.MyInterfaceImpl" + + Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) + Get + Return ImmutableArray.Create(Rule) + End Get + End Property + + Public Overrides Sub Initialize(context As AnalysisContext) + context.RegisterCompilationStartAction( + Sub(compilationContext) + ' We only care about compilations where interface type "DontInheritInterfaceTypeName" is available. + Dim interfaceType = compilationContext.Compilation.GetTypeByMetadataName(DontInheritInterfaceTypeName) + If interfaceType Is Nothing Then + Return + End If + + ' Register an action that accesses the immutable state and reports diagnostics. + compilationContext.RegisterSymbolAction( + Sub(symbolContext) + AnalyzeSymbol(symbolContext, interfaceType) + End Sub, + SymbolKind.NamedType) + End Sub) + End Sub + + Public Sub AnalyzeSymbol(context As SymbolAnalysisContext, interfaceType As INamedTypeSymbol) + ' Check if the symbol implements the interface type + Dim namedType = DirectCast(context.Symbol, INamedTypeSymbol) + If namedType.Interfaces.Contains(interfaceType) AndAlso + Not namedType.ToDisplayString(SymbolDisplayFormat.VisualBasicErrorMessageFormat).Equals(AllowedInternalImplementationTypeName) Then + + Dim diag = Diagnostic.Create(Rule, namedType.Locations(0), namedType.Name, DontInheritInterfaceTypeName) + context.ReportDiagnostic(diag) + End If + End Sub + + End Class +End Namespace diff --git a/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatefulAnalyzers/CompilationStartedAnalyzerWithCompilationWideAnalysis.vb b/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatefulAnalyzers/CompilationStartedAnalyzerWithCompilationWideAnalysis.vb new file mode 100644 index 000000000..e962ee58c --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatefulAnalyzers/CompilationStartedAnalyzerWithCompilationWideAnalysis.vb @@ -0,0 +1,152 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Diagnostics + +Namespace BasicAnalyzers + ''' + ''' Analyzer to demonstrate compilation-wide analysis. + ''' + ''' Analysis scenario: + ''' (a) You have an interface, which is a well-known secure interface, i.e. it is a marker for all secure types in an assembly. + ''' (b) You have a method level attribute which marks the owning method as unsecure. An interface which has any member with such an attribute, must be considered unsecure. + ''' (c) We want to report diagnostics for types implementing the well-known secure interface that also implement any unsecure interface. + ''' + ''' Analyzer performs compilation-wide analysis to detect such violating types and reports diagnostics for them in the compilation end action. + ''' + ''' + ''' The analyzer performs this analysis by registering: + ''' (a) A compilation start action, which initializes per-compilation state: + ''' (i) Immutable state: We fetch and store the type symbols for the well-known secure interface type and unsecure method attribute type in the compilation. + ''' (ii) Mutable state: We maintain a set of all types implementing well-known secure interface type and set of all interface types with an unsecure method. + ''' (b) A compilation symbol action, which identifies all named types that implement the well-known secure interface, and all method symbols that have the unsecure method attribute. + ''' (c) A compilation end action which reports diagnostics for types implementing the well-known secure interface that also implementing any unsecure interface. + ''' + ''' + + Public Class CompilationStartedAnalyzerWithCompilationWideAnalysis + Inherits DiagnosticAnalyzer + +#Region "Descriptor fields" + Friend Shared ReadOnly Title As LocalizableString = "Secure types must not implement interfaces with unsecure methods" + Friend Shared ReadOnly MessageFormat As LocalizableString = "Type '{0}' is a secure type as it implements interface '{1}', but it also implements interface '{2}' which has unsecure method(s)." + Friend Shared ReadOnly Description As LocalizableString = "Secure types must not implement interfaces with unsecure methods." + + Friend Shared Rule As New DiagnosticDescriptor(DiagnosticIds.CompilationStartedAnalyzerWithCompilationWideAnalysisRuleId, Title, MessageFormat, DiagnosticCategories.Stateful, DiagnosticSeverity.Warning, isEnabledByDefault:=True, description:=Description) +#End Region + + Public Const UnsecureMethodAttributeName As String = "MyNamespace.UnsecureMethodAttribute" + Public Const SecureTypeInterfaceName As String = "MyNamespace.ISecureType" + + Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) + Get + Return ImmutableArray.Create(Rule) + End Get + End Property + + Public Overrides Sub Initialize(context As AnalysisContext) + context.RegisterCompilationStartAction( + Sub(compilationContext) + ' Check if the attribute type marking unsecure methods is defined. + Dim unsecureMethodAttributeType = compilationContext.Compilation.GetTypeByMetadataName(UnsecureMethodAttributeName) + If unsecureMethodAttributeType Is Nothing Then + Return + End If + + ' Check if the interface type marking secure types is defined. + Dim secureTypeInterfaceType = compilationContext.Compilation.GetTypeByMetadataName(SecureTypeInterfaceName) + If secureTypeInterfaceType Is Nothing Then + Return + End If + + ' Initialize state in the start action. + Dim analyzer = New CompilationAnalyzer(unsecureMethodAttributeType, secureTypeInterfaceType) + + ' Register an intermediate non-end action that accesses and modifies the state. + compilationContext.RegisterSymbolAction(AddressOf analyzer.AnalyzeSymbol, SymbolKind.NamedType, SymbolKind.Method) + + ' Register an end action to report diagnostics based on the final state. + compilationContext.RegisterCompilationEndAction(AddressOf analyzer.CompilationEndAction) + End Sub) + End Sub + + Private Class CompilationAnalyzer + +#Region "Per-Compilation immutable state" + Private ReadOnly _unsecureMethodAttributeType As INamedTypeSymbol + Private ReadOnly _secureTypeInterfaceType As INamedTypeSymbol +#End Region + +#Region "Per-Compilation mutable state" + ''' + ''' List of secure types in the compilation implementing interface . + ''' + Private _secureTypes As List(Of INamedTypeSymbol) + + ''' + ''' Set of unsecure interface types in the compilation that have methods with an attribute of . + ''' + Private _interfacesWithUnsecureMethods As HashSet(Of INamedTypeSymbol) +#End Region + +#Region "State intialization" + Public Sub New(unsecureMethodAttributeType As INamedTypeSymbol, secureTypeInterfaceType As INamedTypeSymbol) + _unsecureMethodAttributeType = unsecureMethodAttributeType + _secureTypeInterfaceType = secureTypeInterfaceType + + _secureTypes = Nothing + _interfacesWithUnsecureMethods = Nothing + End Sub +#End Region + +#Region "Intermediate actions" + Public Sub AnalyzeSymbol(context As SymbolAnalysisContext) + Select Case context.Symbol.Kind + Case SymbolKind.NamedType + ' Check if the symbol implements "_secureTypeInterfaceType". + Dim namedType = DirectCast(context.Symbol, INamedTypeSymbol) + If namedType.AllInterfaces.Contains(_secureTypeInterfaceType) Then + _secureTypes = If(_secureTypes, New List(Of INamedTypeSymbol)()) + _secureTypes.Add(namedType) + End If + + Exit Select + + Case SymbolKind.Method + ' Check if this is an interface method with "_unsecureMethodAttributeType" attribute. + Dim method = DirectCast(context.Symbol, IMethodSymbol) + If method.ContainingType.TypeKind = TypeKind.Interface AndAlso + method.GetAttributes().Any(Function(a) a.AttributeClass.Equals(_unsecureMethodAttributeType)) Then + _interfacesWithUnsecureMethods = If(_interfacesWithUnsecureMethods, New HashSet(Of INamedTypeSymbol)()) + _interfacesWithUnsecureMethods.Add(method.ContainingType) + End If + + Exit Select + End Select + End Sub +#End Region + +#Region "End action" + Public Sub CompilationEndAction(context As CompilationAnalysisContext) + If _interfacesWithUnsecureMethods Is Nothing OrElse _secureTypes Is Nothing Then + ' No violating types. + Return + End If + + ' Report diagnostic for violating named types. + For Each secureType In _secureTypes + For Each unsecureInterface In _interfacesWithUnsecureMethods + If secureType.AllInterfaces.Contains(unsecureInterface) Then + Dim diag = Diagnostic.Create(Rule, secureType.Locations(0), secureType.Name, SecureTypeInterfaceName, unsecureInterface.Name) + context.ReportDiagnostic(diag) + Exit For + End If + Next + Next + End Sub +#End Region + + End Class + End Class +End Namespace diff --git a/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatelessAnalyzers/CodeBlockAnalyzer.vb b/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatelessAnalyzers/CodeBlockAnalyzer.vb new file mode 100644 index 000000000..8747c2bf4 --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatelessAnalyzers/CodeBlockAnalyzer.vb @@ -0,0 +1,55 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace BasicAnalyzers + ''' + ''' Analyzer for reporting code block diagnostics. + ''' It reports diagnostics for all redundant methods which have an empty method body and are not virtual/override. + ''' + ''' + ''' For analyzers that requires analyzing symbols or syntax nodes across a code block, see . + ''' + + Public Class CodeBlockAnalyzer + Inherits DiagnosticAnalyzer + +#Region "Descriptor fields" + Friend Shared ReadOnly Title As LocalizableString = "Remove unnecessary methods" + Friend Shared ReadOnly MessageFormat As LocalizableString = "Method '{0}' is a non-virtual method with an empty body. Consider removing this method from your assembly." + Friend Shared ReadOnly Description As LocalizableString = "Remove unnecessary methods." + + Friend Shared Rule As New DiagnosticDescriptor(DiagnosticIds.CodeBlockAnalyzerRuleId, Title, MessageFormat, DiagnosticCategories.Stateless, DiagnosticSeverity.Warning, isEnabledByDefault:=True, description:=Description) +#End Region + + Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) + Get + Return ImmutableArray.Create(Rule) + End Get + End Property + + Public Overrides Sub Initialize(context As AnalysisContext) + context.RegisterCodeBlockAction(AddressOf CodeBlockAction) + End Sub + + Private Shared Sub CodeBlockAction(codeBlockContext As CodeBlockAnalysisContext) + ' We only care about method bodies. + If codeBlockContext.OwningSymbol.Kind <> SymbolKind.Method Then + Return + End If + + ' Report diagnostic for void non-virtual methods with empty method bodies. + Dim method = DirectCast(codeBlockContext.OwningSymbol, IMethodSymbol) + Dim block = TryCast(codeBlockContext.CodeBlock, MethodBlockBaseSyntax) + If method.ReturnsVoid AndAlso Not method.IsVirtual AndAlso block?.Statements.Count = 0 Then + Dim tree = block.SyntaxTree + Dim location = method.Locations.First(Function(l) tree.Equals(l.SourceTree)) + Dim diag = Diagnostic.Create(Rule, location, method.Name) + codeBlockContext.ReportDiagnostic(diag) + End If + End Sub + End Class +End Namespace diff --git a/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatelessAnalyzers/CompilationAnalyzer.vb b/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatelessAnalyzers/CompilationAnalyzer.vb new file mode 100644 index 000000000..6f455da2d --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatelessAnalyzers/CompilationAnalyzer.vb @@ -0,0 +1,65 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Diagnostics + +Namespace BasicAnalyzers + ''' + ''' Analyzer for reporting compilation diagnostics. + ''' It reports diagnostics for analyzer diagnostics that have been suppressed for the entire compilation. + ''' + ''' + ''' For analyzers that requires analyzing symbols or syntax nodes across compilation, see and . + ''' + + Public Class CompilationAnalyzer + Inherits DiagnosticAnalyzer + +#Region "Descriptor fields" + Friend Shared ReadOnly Title As LocalizableString = "Dont suppress analyzer diagnostics" + Friend Shared ReadOnly MessageFormat As LocalizableString = "Analyzer diagnostic '{0}' is suppressed, consider removing this compilation wide suppression." + Friend Shared ReadOnly Description As LocalizableString = "Do not suppress analyzer diagnostics." + + Friend Shared Rule As New DiagnosticDescriptor(DiagnosticIds.CompilationAnalyzerRuleId, Title, MessageFormat, DiagnosticCategories.Stateless, DiagnosticSeverity.Warning, isEnabledByDefault:=True, description:=Description) +#End Region + + Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) + Get + Return ImmutableArray.Create(Rule) + End Get + End Property + + Public Overrides Sub Initialize(context As AnalysisContext) + context.RegisterCompilationAction(AddressOf AnalyzeCompilation) + End Sub + + Private Shared Sub AnalyzeCompilation(context As CompilationAnalysisContext) + ' Get all the suppressed analyzer diagnostic IDs. + Dim suppressedAnalyzerDiagnosticIds = GetSuppressedAnalyzerDiagnosticIds(context.Compilation.Options.SpecificDiagnosticOptions) + + For Each suppressedDiagnosticId In suppressedAnalyzerDiagnosticIds + ' For all such suppressed diagnostic IDs, produce a diagnostic. + Dim diag = Diagnostic.Create(Rule, Location.None, suppressedDiagnosticId) + context.ReportDiagnostic(diag) + Next + End Sub + + Private Shared Iterator Function GetSuppressedAnalyzerDiagnosticIds(specificOptions As ImmutableDictionary(Of String, ReportDiagnostic)) As IEnumerable(Of String) + For Each kvp In specificOptions + If kvp.Value = ReportDiagnostic.Suppress Then + Dim intId As Integer + If kvp.Key.StartsWith("CS", StringComparison.OrdinalIgnoreCase) AndAlso Integer.TryParse(kvp.Key.Substring(2), intId) Then + Continue For + End If + + If kvp.Key.StartsWith("BC", StringComparison.OrdinalIgnoreCase) AndAlso Integer.TryParse(kvp.Key.Substring(2), intId) Then + Continue For + End If + + Yield kvp.Key + End If + Next + End Function + End Class +End Namespace diff --git a/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SemanticModelAnalyzer.vb b/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SemanticModelAnalyzer.vb new file mode 100644 index 000000000..5f596140e --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SemanticModelAnalyzer.vb @@ -0,0 +1,45 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports System.IO +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Diagnostics + +Namespace BasicAnalyzers + ''' + ''' Analyzer for reporting syntax tree diagnostics, that require some semantic analysis. + ''' It reports diagnostics for all source files which have at least one declaration diagnostic. + ''' + + Public Class SemanticModelAnalyzer + Inherits DiagnosticAnalyzer + +#Region "Descriptor fields" + Friend Shared ReadOnly Title As LocalizableString = "Source file declaration diagnostics count" + Friend Shared ReadOnly MessageFormat As LocalizableString = "Source file '{0}' has '{1}' declaration diagnostic(s)." + Friend Shared ReadOnly Description As LocalizableString = "Source file declaration diagnostic count." + + Friend Shared Rule As New DiagnosticDescriptor(DiagnosticIds.SemanticModelAnalyzerRuleId, Title, MessageFormat, DiagnosticCategories.Stateless, DiagnosticSeverity.Warning, isEnabledByDefault:=True, description:=Description) +#End Region + + Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) + Get + Return ImmutableArray.Create(Rule) + End Get + End Property + + Public Overrides Sub Initialize(context As AnalysisContext) + context.RegisterSemanticModelAction(AddressOf AnalyzeSemanticModel) + End Sub + + Private Shared Sub AnalyzeSemanticModel(context As SemanticModelAnalysisContext) + ' Find just those source files with declaration diagnostics. + Dim diagnosticsCount = context.SemanticModel.GetDeclarationDiagnostics().Length + If diagnosticsCount > 0 Then + ' For all such files, produce a diagnostic. + Dim diag = Diagnostic.Create(Rule, Location.None, Path.GetFileName(context.SemanticModel.SyntaxTree.FilePath), diagnosticsCount) + context.ReportDiagnostic(diag) + End If + End Sub + End Class +End Namespace diff --git a/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SymbolAnalyzer.vb b/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SymbolAnalyzer.vb new file mode 100644 index 000000000..d720b4a35 --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SymbolAnalyzer.vb @@ -0,0 +1,48 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Diagnostics + +Namespace BasicAnalyzers + ''' + ''' Analyzer for reporting symbol diagnostics. + ''' It reports diagnostics for named type symbols that have members with the same name as the named type. + ''' + ''' + ''' For analyzers that requires analyzing symbols or syntax nodes across compilation, see and . + ''' + + Public Class SymbolAnalyzer + Inherits DiagnosticAnalyzer + +#Region "Descriptor fields" + Friend Shared ReadOnly Title As LocalizableString = "Do not declare members with same name as containing type" + Friend Shared ReadOnly MessageFormat As LocalizableString = "Type '{0}' has one or more members with the same name, considering renaming the type or the members." + Friend Shared ReadOnly Description As LocalizableString = "Do not declare members with same name as containing type." + + Friend Shared Rule As New DiagnosticDescriptor(DiagnosticIds.SymbolAnalyzerRuleId, Title, MessageFormat, DiagnosticCategories.Stateless, DiagnosticSeverity.Warning, isEnabledByDefault:=True, description:=Description) +#End Region + + Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) + Get + Return ImmutableArray.Create(Rule) + End Get + End Property + + Public Overrides Sub Initialize(context As AnalysisContext) + context.RegisterSymbolAction(AddressOf AnalyzeSymbol, SymbolKind.NamedType) + End Sub + + Private Shared Sub AnalyzeSymbol(context As SymbolAnalysisContext) + Dim namedTypeSymbol = DirectCast(context.Symbol, INamedTypeSymbol) + + ' Find just those named type symbols that have members with the same name as the named type. + If namedTypeSymbol.GetMembers(namedTypeSymbol.Name).Any() Then + ' For all such symbols, report a diagnostic. + Dim diag = Diagnostic.Create(Rule, namedTypeSymbol.Locations(0), namedTypeSymbol.Name) + context.ReportDiagnostic(diag) + End If + End Sub + End Class +End Namespace diff --git a/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SyntaxNodeAnalyzer.vb b/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SyntaxNodeAnalyzer.vb new file mode 100644 index 000000000..8b26376e4 --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SyntaxNodeAnalyzer.vb @@ -0,0 +1,52 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace BasicAnalyzers + ''' + ''' Analyzer for reporting syntax node diagnostics. + ''' It reports diagnostics for implicitly typed local variables, recommending explicit type specification. + ''' + ''' + ''' For analyzers that requires analyzing symbols or syntax nodes across compilation, see and . + ''' For analyzers that requires analyzing symbols or syntax nodes across a code block, see . + ''' + + Public Class SyntaxNodeAnalyzer + Inherits DiagnosticAnalyzer + +#Region "Descriptor fields" + Friend Shared ReadOnly Title As LocalizableString = "Declare explicit type for local declarations" + Friend Shared ReadOnly MessageFormat As LocalizableString = "Local '{0}' is implicitly typed. Consider specifying its type explicitly in the declaration." + Friend Shared ReadOnly Description As LocalizableString = "Declare explicit type for local declarations." + + Friend Shared Rule As New DiagnosticDescriptor(SyntaxNodeAnalyzerRuleId, Title, MessageFormat, Stateless, DiagnosticSeverity.Warning, isEnabledByDefault:=True, description:=Description) +#End Region + + Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) + Get + Return ImmutableArray.Create(Rule) + End Get + End Property + + Public Overrides Sub Initialize(context As AnalysisContext) + context.RegisterSyntaxNodeAction(AddressOf AnalyzeSyntaxNode, SyntaxKind.VariableDeclarator) + End Sub + + Private Shared Sub AnalyzeSyntaxNode(context As SyntaxNodeAnalysisContext) + ' Find implicitly typed variable declarations. + Dim declaration = DirectCast(context.Node, VariableDeclaratorSyntax) + If declaration.AsClause Is Nothing Then + For Each variable In declaration.Names + ' For all such locals, report a diagnostic. + Dim diag = Diagnostic.Create(Rule, variable.GetLocation(), variable.Identifier.ValueText) + context.ReportDiagnostic(diag) + Next + End If + End Sub + End Class +End Namespace diff --git a/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SyntaxTreeAnalyzer.vb b/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SyntaxTreeAnalyzer.vb new file mode 100644 index 000000000..008ebcde8 --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Implementation/StatelessAnalyzers/SyntaxTreeAnalyzer.vb @@ -0,0 +1,44 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports System.IO +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Diagnostics + +Namespace BasicAnalyzers + ''' + ''' Analyzer for reporting syntax tree diagnostics. + ''' It reports diagnostics for all source files which have documentation comment diagnostics turned off. + ''' + + Public Class SyntaxTreeAnalyzer + Inherits DiagnosticAnalyzer + +#Region "Descriptor fields" + Friend Shared ReadOnly Title As LocalizableString = "Do not suppress documentation comment diagnostics" + Friend Shared ReadOnly MessageFormat As LocalizableString = "Enable documentation comment diagnostics on source file '{0}'." + Friend Shared ReadOnly Description As LocalizableString = "Do not suppress documentation comment diagnostics." + + Friend Shared Rule As New DiagnosticDescriptor(DiagnosticIds.SyntaxTreeAnalyzerRuleId, Title, MessageFormat, DiagnosticCategories.Stateless, DiagnosticSeverity.Warning, isEnabledByDefault:=True, description:=Description) +#End Region + + Public Overrides ReadOnly Property SupportedDiagnostics() As ImmutableArray(Of DiagnosticDescriptor) + Get + Return ImmutableArray.Create(Rule) + End Get + End Property + + Public Overrides Sub Initialize(context As AnalysisContext) + context.RegisterSyntaxTreeAction(AddressOf AnalyzeSyntaxTree) + End Sub + + Private Shared Sub AnalyzeSyntaxTree(context As SyntaxTreeAnalysisContext) + ' Find source files with documentation comment diagnostics turned off. + If context.Tree.Options.DocumentationMode <> DocumentationMode.Diagnose Then + ' For all such files, produce a diagnostic. + Dim diag = Diagnostic.Create(Rule, Location.None, Path.GetFileName(context.Tree.FilePath)) + context.ReportDiagnostic(diag) + End If + End Sub + End Class +End Namespace diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/install.ps1 b/samples/VisualBasic/Analyzers/Analyzers.Implementation/tools/install.ps1 similarity index 100% rename from src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/install.ps1 rename to samples/VisualBasic/Analyzers/Analyzers.Implementation/tools/install.ps1 diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/uninstall.ps1 b/samples/VisualBasic/Analyzers/Analyzers.Implementation/tools/uninstall.ps1 similarity index 100% rename from src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/uninstall.ps1 rename to samples/VisualBasic/Analyzers/Analyzers.Implementation/tools/uninstall.ps1 diff --git a/samples/VisualBasic/Analyzers/Analyzers.Vsix/Analyzers.VisualBasic.Vsix.vbproj b/samples/VisualBasic/Analyzers/Analyzers.Vsix/Analyzers.VisualBasic.Vsix.vbproj new file mode 100644 index 000000000..3ee0bffa9 --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Vsix/Analyzers.VisualBasic.Vsix.vbproj @@ -0,0 +1,19 @@ + + + + net472 + Analyzers.VisualBasic.Vsix + Analyzers.VisualBasic + false + false + false + false + false + + + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/Analyzers/Analyzers.Vsix/My Project/AssemblyInfo.vb b/samples/VisualBasic/Analyzers/Analyzers.Vsix/My Project/AssemblyInfo.vb new file mode 100644 index 000000000..ceea715a2 --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Vsix/My Project/AssemblyInfo.vb @@ -0,0 +1,32 @@ +Imports System +Imports System.Reflection +Imports System.Runtime.InteropServices + +' General Information about an assembly is controlled through the following +' set of attributes. Change these attribute values to modify the information +' associated with an assembly. + +' Review the values of the assembly attributes + + + + + + + + + + +' Version information for an assembly consists of the following four values: +' +' Major Version +' Minor Version +' Build Number +' Revision +' +' You can specify all the values or you can default the Build and Revision Numbers +' by using the '*' as shown below: +' + + + diff --git a/samples/VisualBasic/Analyzers/Analyzers.Vsix/source.extension.vsixmanifest b/samples/VisualBasic/Analyzers/Analyzers.Vsix/source.extension.vsixmanifest new file mode 100644 index 000000000..5f29a4e7f --- /dev/null +++ b/samples/VisualBasic/Analyzers/Analyzers.Vsix/source.extension.vsixmanifest @@ -0,0 +1,22 @@ + + + + + Samples.Analyzers + This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn"). + + + + + + + + + + + + + + + + diff --git a/samples/VisualBasic/ConsoleClassifier/ConsoleClassifier.VisualBasic.vbproj b/samples/VisualBasic/ConsoleClassifier/ConsoleClassifier.VisualBasic.vbproj new file mode 100644 index 000000000..773eebfae --- /dev/null +++ b/samples/VisualBasic/ConsoleClassifier/ConsoleClassifier.VisualBasic.vbproj @@ -0,0 +1,14 @@ + + + + Exe + net472 + + + + + + + + + diff --git a/samples/VisualBasic/ConsoleClassifier/Program.vb b/samples/VisualBasic/ConsoleClassifier/Program.vb new file mode 100644 index 000000000..c2ded13c4 --- /dev/null +++ b/samples/VisualBasic/ConsoleClassifier/Program.vb @@ -0,0 +1,79 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Classification +Imports Microsoft.CodeAnalysis.Formatting +Imports Microsoft.CodeAnalysis.Text + +Friend Module Program + + Public Sub Main(args As String()) + TestFormatterAndClassifierAsync().GetAwaiter.GetResult() + End Sub + + Public Async Function TestFormatterAndClassifierAsync() As Task + Dim workspace = New AdhocWorkspace() + Dim solution = workspace.CurrentSolution + Dim project = solution.AddProject("projectName", "assemblyName", LanguageNames.VisualBasic) + Dim document = project.AddDocument("name.vb", +"Module M +Sub Main() +WriteLine(""Hello, World!"") +End Sub +End Module") + document = Await Formatter.FormatAsync(document) + Dim text As SourceText = Await document.GetTextAsync() + + Dim classifiedSpans As IEnumerable(Of ClassifiedSpan) = Await Classifier.GetClassifiedSpansAsync(document, TextSpan.FromBounds(0, text.Length)) + Console.BackgroundColor = ConsoleColor.Black + + Dim ranges = From span As ClassifiedSpan In classifiedSpans + Select New Range(span, text.GetSubText(span.TextSpan).ToString()) + + ' Whitespace isn't classified so fill in ranges for whitespace. + ranges = FillGaps(text, ranges) + + For Each range As Range In ranges + Select Case range.ClassificationType + Case "keyword" + Console.ForegroundColor = ConsoleColor.DarkCyan + Case "class name", "module name" + Console.ForegroundColor = ConsoleColor.Cyan + Case "string" + Console.ForegroundColor = ConsoleColor.DarkYellow + Case Else + Console.ForegroundColor = ConsoleColor.White + End Select + + Console.Write(range.Text) + Next + + Console.ResetColor() + Console.WriteLine() + End Function + + Public Iterator Function FillGaps(text As SourceText, ranges As IEnumerable(Of Range)) As IEnumerable(Of Range) + Const whitespaceClassification As String = Nothing + + Dim current As Integer = 0 + Dim previous As Range = Nothing + + For Each range As Range In ranges + Dim start As Integer = range.TextSpan.Start + If start > current Then + Yield New Range(whitespaceClassification, TextSpan.FromBounds(current, start), text) + End If + + If previous Is Nothing OrElse range.TextSpan <> previous.TextSpan Then + Yield range + End If + + previous = range + current = range.TextSpan.End + Next + + If current < text.Length Then + Yield New Range(whitespaceClassification, TextSpan.FromBounds(current, text.Length), text) + End If + End Function +End Module diff --git a/samples/VisualBasic/ConsoleClassifier/Range.vb b/samples/VisualBasic/ConsoleClassifier/Range.vb new file mode 100644 index 000000000..de75f613f --- /dev/null +++ b/samples/VisualBasic/ConsoleClassifier/Range.vb @@ -0,0 +1,36 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.Classification +Imports Microsoft.CodeAnalysis.Text + +Friend Class Range + + Public ReadOnly Property ClassifiedSpan As ClassifiedSpan + + Public ReadOnly Property Text As String + + Public Sub New(classification As String, span As TextSpan, text As SourceText) + Me.New(classification, span, text.GetSubText(span).ToString()) + End Sub + + Public Sub New(classification As String, span As TextSpan, text As String) + Me.New(New ClassifiedSpan(classification, span), text) + End Sub + + Public Sub New(classifiedSpan As ClassifiedSpan, text As String) + _ClassifiedSpan = classifiedSpan + _Text = text + End Sub + + Public ReadOnly Property ClassificationType As String + Get + Return ClassifiedSpan.ClassificationType + End Get + End Property + + Public ReadOnly Property TextSpan As TextSpan + Get + Return ClassifiedSpan.TextSpan + End Get + End Property +End Class diff --git a/samples/VisualBasic/ConvertToAutoProperty/ConvertToAutoProperty.Vsix/ConvertToAutoProperty.VisualBasic.Vsix.vbproj b/samples/VisualBasic/ConvertToAutoProperty/ConvertToAutoProperty.Vsix/ConvertToAutoProperty.VisualBasic.Vsix.vbproj new file mode 100644 index 000000000..7410ccb88 --- /dev/null +++ b/samples/VisualBasic/ConvertToAutoProperty/ConvertToAutoProperty.Vsix/ConvertToAutoProperty.VisualBasic.Vsix.vbproj @@ -0,0 +1,23 @@ + + + + + net472 + ConvertToAutoProperty.VisualBasic.Vsix + ConvertToAutoProperty.VisualBasic.Vsix + false + false + false + false + false + + + + + Designer + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/ConvertToAutoProperty/ConvertToAutoProperty.Vsix/My Project/AssemblyInfo.vb b/samples/VisualBasic/ConvertToAutoProperty/ConvertToAutoProperty.Vsix/My Project/AssemblyInfo.vb new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/samples/VisualBasic/ConvertToAutoProperty/ConvertToAutoProperty.Vsix/My Project/AssemblyInfo.vb @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/samples/VisualBasic/ConvertToAutoProperty/ConvertToAutoProperty.Vsix/source.extension.vsixmanifest b/samples/VisualBasic/ConvertToAutoProperty/ConvertToAutoProperty.Vsix/source.extension.vsixmanifest new file mode 100644 index 000000000..68c4bdf1b --- /dev/null +++ b/samples/VisualBasic/ConvertToAutoProperty/ConvertToAutoProperty.Vsix/source.extension.vsixmanifest @@ -0,0 +1,21 @@ + + + + + Convert to Auto Property for CSharp + This is a sample C# code refactoring that converts properties to auto-implemented properties using the .NET Compiler Platform ("Roslyn"). + + + + + + + + + + + + + + + diff --git a/samples/VisualBasic/ConvertToAutoProperty/ConvertToAutoProperty/CodeRefactoringProvider.vb b/samples/VisualBasic/ConvertToAutoProperty/ConvertToAutoProperty/CodeRefactoringProvider.vb new file mode 100644 index 000000000..0b9446545 --- /dev/null +++ b/samples/VisualBasic/ConvertToAutoProperty/ConvertToAutoProperty/CodeRefactoringProvider.vb @@ -0,0 +1,189 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Composition +Imports System.Threading +Imports System.Threading.Tasks +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeActions +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Formatting +Imports Microsoft.CodeAnalysis.FindSymbols +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + + +Class ConvertToAutoPropertyCodeRefactoringProvider + Inherits CodeRefactoringProvider + + Public NotOverridable Overrides Async Function ComputeRefactoringsAsync(context As CodeRefactoringContext) As Task + Dim document = context.Document + Dim textSpan = context.Span + Dim cancellationToken = context.CancellationToken + + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim token = root.FindToken(textSpan.Start) + If token.Parent Is Nothing Then + Return + End If + + Dim propertyBlock = token.Parent.FirstAncestorOrSelf(Of PropertyBlockSyntax)() + If propertyBlock Is Nothing OrElse Not HasBothAccessors(propertyBlock) OrElse Not propertyBlock.Span.IntersectsWith(textSpan.Start) Then + Return + End If + + ' TODO: Check that the property can be converted to an auto-property. + ' It should be a simple property with a getter and setter that simply retrieves + ' and assigns a backing field. In addition, the backing field should be private. + + context.RegisterRefactoring( + New ConvertToAutopropertyCodeAction("Convert to auto property", + Function(c) ConvertToAutoAsync(document, propertyBlock, c))) + End Function + + ''' + ''' Returns true if both get and set accessors exist with a single statement on the given property; otherwise false. + ''' + Private Shared Function HasBothAccessors(propertyBlock As PropertyBlockSyntax) As Boolean + Dim accessors = propertyBlock.Accessors + Dim getter = accessors.FirstOrDefault(Function(node) node.Kind() = SyntaxKind.GetAccessorBlock) + Dim setter = accessors.FirstOrDefault(Function(node) node.Kind() = SyntaxKind.SetAccessorBlock) + + Return getter IsNot Nothing AndAlso setter IsNot Nothing + End Function + + Private Async Function ConvertToAutoAsync(document As Document, propertyBlock As PropertyBlockSyntax, cancellationToken As CancellationToken) As Task(Of Document) + ' First, annotate the property block so that we can get back to it later. + Dim propertyAnnotation = New SyntaxAnnotation() + Dim oldRoot = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim newRoot = oldRoot.ReplaceNode(propertyBlock, propertyBlock.WithAdditionalAnnotations(propertyAnnotation)) + + document = document.WithSyntaxRoot(newRoot) + + ' Find the backing field of the property + Dim backingField = Await GetBackingFieldAsync(document, propertyAnnotation, cancellationToken).ConfigureAwait(False) + + ' Retrieve the initializer of the backing field + Dim modifiedIdentifier = CType(backingField.DeclaringSyntaxReferences.Single().GetSyntax(), ModifiedIdentifierSyntax) + Dim variableDeclarator = CType(modifiedIdentifier.Parent, VariableDeclaratorSyntax) + Dim initializer = variableDeclarator.Initializer + + ' Update all references to the backing field to point to the property name + document = Await UpdateBackingFieldReferencesAsync(document, backingField, propertyAnnotation, cancellationToken).ConfigureAwait(False) + + ' Remove the backing field declaration + document = Await RemoveBackingFieldDeclarationAsync(document, backingField, cancellationToken).ConfigureAwait(False) + + ' Finally, replace the property with an auto property + document = Await ReplacePropertyWithAutoPropertyAsync(document, initializer, propertyAnnotation, cancellationToken).ConfigureAwait(False) + + Return document + End Function + + Private Shared Async Function GetAnnotatedPropertyBlockAsync(document As Document, propertyAnnotation As SyntaxAnnotation, cancellationToken As CancellationToken) As Task(Of PropertyBlockSyntax) + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim annotatedNode = root.GetAnnotatedNodesAndTokens(propertyAnnotation).Single().AsNode() + Return CType(annotatedNode, PropertyBlockSyntax) + End Function + + Private Async Function GetBackingFieldAsync(document As Document, propertyAnnotation As SyntaxAnnotation, cancellationToken As CancellationToken) As Task(Of IFieldSymbol) + Dim propertyBlock = Await GetAnnotatedPropertyBlockAsync(document, propertyAnnotation, cancellationToken).ConfigureAwait(False) + Dim propertyGetter = propertyBlock.Accessors.FirstOrDefault(Function(node) node.Kind() = SyntaxKind.GetAccessorBlock) + + Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False) + Dim containingType = semanticModel.GetDeclaredSymbol(propertyBlock).ContainingType + + Dim statements = propertyGetter.Statements + If statements.Count = 1 Then + Dim returnStatement = TryCast(statements.FirstOrDefault(), ReturnStatementSyntax) + + If returnStatement IsNot Nothing AndAlso returnStatement.Expression IsNot Nothing Then + Dim symbol = semanticModel.GetSymbolInfo(returnStatement.Expression).Symbol + Dim fieldSymbol = TryCast(symbol, IFieldSymbol) + + If fieldSymbol IsNot Nothing AndAlso fieldSymbol.ContainingType.Equals(containingType) Then + Return fieldSymbol + End If + End If + End If + + Return Nothing + End Function + + Private Async Function UpdateBackingFieldReferencesAsync(document As Document, backingField As IFieldSymbol, propertyAnnotation As SyntaxAnnotation, cancellationToken As CancellationToken) As Task(Of Document) + Dim propertyBlock = Await GetAnnotatedPropertyBlockAsync(document, propertyAnnotation, cancellationToken).ConfigureAwait(False) + Dim propertyName = propertyBlock.PropertyStatement.Identifier.ValueText + + Dim oldRoot = DirectCast(Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False), SyntaxNode) + Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False) + + Dim referenceRewriter = New referenceRewriter(propertyName, backingField, semanticModel) + Dim newRoot = referenceRewriter.Visit(oldRoot) + + Return document.WithSyntaxRoot(newRoot) + End Function + + Private Async Function RemoveBackingFieldDeclarationAsync(document As Document, backingField As IFieldSymbol, cancellationToken As CancellationToken) As Task(Of Document) + Dim compilation = Await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(False) + backingField = SymbolFinder.FindSimilarSymbols(backingField, compilation, cancellationToken).FirstOrDefault() + If backingField Is Nothing Then + Return document + End If + + Dim modifiedIdentifier = CType(backingField.DeclaringSyntaxReferences.Single().GetSyntax(), ModifiedIdentifierSyntax) + Dim variableDeclarator = CType(modifiedIdentifier.Parent, VariableDeclaratorSyntax) + Dim fieldDeclaration = CType(variableDeclarator.Parent, FieldDeclarationSyntax) + + Dim oldRoot = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + + Dim newRoot As SyntaxNode = Nothing + If variableDeclarator.Names.Count > 1 Then + Dim newVariableDeclarator = variableDeclarator.RemoveNode(modifiedIdentifier, SyntaxRemoveOptions.KeepExteriorTrivia) + newVariableDeclarator = newVariableDeclarator.WithAdditionalAnnotations(Formatter.Annotation) + newRoot = oldRoot.ReplaceNode(variableDeclarator, newVariableDeclarator) + ElseIf fieldDeclaration.Declarators.Count > 1 Then + Dim newFieldDeclaration = fieldDeclaration.RemoveNode(variableDeclarator, SyntaxRemoveOptions.KeepExteriorTrivia) + newFieldDeclaration = newFieldDeclaration.WithAdditionalAnnotations(Formatter.Annotation) + newRoot = oldRoot.ReplaceNode(fieldDeclaration, newFieldDeclaration) + Else + newRoot = oldRoot.RemoveNode(fieldDeclaration, SyntaxRemoveOptions.KeepExteriorTrivia) + End If + + Return document.WithSyntaxRoot(newRoot) + End Function + + Private Shared Async Function ReplacePropertyWithAutoPropertyAsync(document As Document, initializer As EqualsValueSyntax, propertyAnnotation As SyntaxAnnotation, cancellationToken As CancellationToken) As Task(Of Document) + Dim propertyBlock = Await GetAnnotatedPropertyBlockAsync(document, propertyAnnotation, cancellationToken).ConfigureAwait(False) + + Dim autoProperty = propertyBlock.PropertyStatement _ + .WithInitializer(initializer) _ + .WithAdditionalAnnotations(Formatter.Annotation) + + Dim oldRoot = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim newRoot = oldRoot.ReplaceNode(propertyBlock, autoProperty) + + Return document.WithSyntaxRoot(newRoot) + End Function + + Private Class ConvertToAutopropertyCodeAction + Inherits CodeAction + + Private ReadOnly generateDocument As Func(Of CancellationToken, Task(Of Document)) + Private ReadOnly _title As String + + Public Overrides ReadOnly Property Title As String + Get + Return _title + End Get + End Property + + Public Sub New(title As String, generateDocument As Func(Of CancellationToken, Task(Of Document))) + _title = title + Me.generateDocument = generateDocument + End Sub + + Protected Overrides Function GetChangedDocumentAsync(cancellationToken As CancellationToken) As Task(Of Document) + Return generateDocument(cancellationToken) + End Function + End Class +End Class diff --git a/samples/VisualBasic/ConvertToAutoProperty/ConvertToAutoProperty/ConvertToAutoProperty.VisualBasic.vbproj b/samples/VisualBasic/ConvertToAutoProperty/ConvertToAutoProperty/ConvertToAutoProperty.VisualBasic.vbproj new file mode 100644 index 000000000..3a1a5c970 --- /dev/null +++ b/samples/VisualBasic/ConvertToAutoProperty/ConvertToAutoProperty/ConvertToAutoProperty.VisualBasic.vbproj @@ -0,0 +1,12 @@ + + + + netstandard1.3 + + + + + + + + diff --git a/samples/VisualBasic/ConvertToAutoProperty/ConvertToAutoProperty/ReferenceRewriter.vb b/samples/VisualBasic/ConvertToAutoProperty/ConvertToAutoProperty/ReferenceRewriter.vb new file mode 100644 index 000000000..255a3d9b1 --- /dev/null +++ b/samples/VisualBasic/ConvertToAutoProperty/ConvertToAutoProperty/ReferenceRewriter.vb @@ -0,0 +1,35 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Formatting +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Friend Class ReferenceRewriter + Inherits VisualBasicSyntaxRewriter + + Private ReadOnly _name As String + Private ReadOnly _symbol As ISymbol + Private ReadOnly _semanticModel As SemanticModel + + Public Sub New(name As String, symbol As ISymbol, semanticModel As SemanticModel) + _name = name + _symbol = symbol + _semanticModel = semanticModel + End Sub + + Public Overrides Function VisitIdentifierName(identifierName As IdentifierNameSyntax) As SyntaxNode + If identifierName.Identifier.ValueText = _symbol.Name Then + Dim identifierSymbol = _semanticModel.GetSymbolInfo(identifierName).Symbol + If identifierSymbol IsNot Nothing AndAlso identifierSymbol.Equals(_symbol) Then + identifierName = identifierName.WithIdentifier( + SyntaxFactory.Identifier(_name)) + + Return identifierName.WithAdditionalAnnotations(Formatter.Annotation) + End If + End If + + Return identifierName + End Function + +End Class diff --git a/samples/VisualBasic/FormatSolution/FormatSolution.VisualBasic.vbproj b/samples/VisualBasic/FormatSolution/FormatSolution.VisualBasic.vbproj new file mode 100644 index 000000000..5c08299af --- /dev/null +++ b/samples/VisualBasic/FormatSolution/FormatSolution.VisualBasic.vbproj @@ -0,0 +1,15 @@ + + + + Exe + net472 + + + + + + + + + + diff --git a/samples/VisualBasic/FormatSolution/FormatSolution.vb b/samples/VisualBasic/FormatSolution/FormatSolution.vb new file mode 100644 index 000000000..5c42612dd --- /dev/null +++ b/samples/VisualBasic/FormatSolution/FormatSolution.vb @@ -0,0 +1,55 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.Build.Locator +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Formatting +Imports Microsoft.CodeAnalysis.MSBuild + +' This program will format all Visual Basic and C# source files for an entire solution. +Module Program + + Sub Main(args As String()) + ' Locate and register the default instance of MSBuild installed on this machine. + MSBuildLocator.RegisterDefaults() + + ' The test solution is copied to the output directory when you build this sample. + Dim workspace As MSBuildWorkspace = MSBuildWorkspace.Create() + + ' Open the solution within the workspace. + Dim originalSolution As Solution = workspace.OpenSolutionAsync(args(0)).Result + + ' Declare a variable to store the intermediate solution snapshot at each step. + Dim newSolution As Solution = originalSolution + + ' Note how we can't simply iterate over originalSolution.Projects or project.Documents + ' because it will return objects from the unmodified originalSolution, Not from the newSolution. + ' We need to use the ProjectIds And DocumentIds (that don't change) to look up the corresponding + ' snapshots in the newSolution. + For Each projectId As ProjectId In originalSolution.ProjectIds + ' Look up the snapshot for the original project in the latest forked solution. + Dim project As Project = newSolution.GetProject(projectId) + + For Each documentId As DocumentId In project.DocumentIds + + ' Look up the snapshot for the original document in the latest forked solution. + Dim document As Document = newSolution.GetDocument(documentId) + + ' Get a transformed version of the document (a new solution snapshot is created + ' under the covers to contain it - none of the existing objects are modified). + Dim newDocument As Document = Formatter.FormatAsync(document).Result + + ' Store the solution implicitly constructed in the previous step as the latest + ' one so we can continue building it up in the next iteration. + newSolution = newDocument.Project.Solution + Next + Next + + ' Actually apply the accumulated changes And save them to disk. At this point + ' workspace.CurrentSolution Is updated to point to the New solution. + If workspace.TryApplyChanges(newSolution) Then + Console.WriteLine("Solution updated.") + Else + Console.WriteLine("Update failed!") + End If + End Sub +End Module diff --git a/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.Vsix/ImplementNotifyPropertyChanged.VisualBasic.Vsix.vbproj b/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.Vsix/ImplementNotifyPropertyChanged.VisualBasic.Vsix.vbproj new file mode 100644 index 000000000..bc4f86987 --- /dev/null +++ b/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.Vsix/ImplementNotifyPropertyChanged.VisualBasic.Vsix.vbproj @@ -0,0 +1,22 @@ + + + + net472 + ImplementNotifyPropertyChanged.VisualBasic.Vsix + ImplementNotifyPropertyChanged.VisualBasic.Vsix + false + false + false + false + false + + + + + Designer + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.Vsix/My Project/AssemblyInfo.vb b/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.Vsix/My Project/AssemblyInfo.vb new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.Vsix/My Project/AssemblyInfo.vb @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.Vsix/source.extension.vsixmanifest b/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.Vsix/source.extension.vsixmanifest new file mode 100644 index 000000000..0b1dc682a --- /dev/null +++ b/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.Vsix/source.extension.vsixmanifest @@ -0,0 +1,21 @@ + + + + + Implement INotifyPropertyChanged for Visual Basic + This is a sample extension to implement INotifyPropertyChanged using the .NET Compiler Platform ("Roslyn"). + + + + + + + + + + + + + + + diff --git a/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/CodeGeneration.vb b/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/CodeGeneration.vb new file mode 100644 index 000000000..3a248a74b --- /dev/null +++ b/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/CodeGeneration.vb @@ -0,0 +1,294 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.ComponentModel +Imports System.Runtime.CompilerServices +Imports System.Threading +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Editing +Imports Microsoft.CodeAnalysis.Formatting +Imports Microsoft.CodeAnalysis.Simplification +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Friend Module CodeGeneration + + Friend Function ImplementINotifyPropertyChanged( + root As CompilationUnitSyntax, + model As SemanticModel, + properties As IEnumerable(Of ExpandablePropertyInfo), + Workspace As Workspace) As CompilationUnitSyntax + + Dim typeDeclaration = properties.First().PropertyDeclaration.FirstAncestorOrSelf(Of TypeBlockSyntax) + Dim backingFieldLookup = properties.ToDictionary(Function(info) info.PropertyDeclaration, Function(info) info.BackingFieldName) + Dim allProperties = properties.Select(Function(p) DirectCast(p.PropertyDeclaration, SyntaxNode)).Concat({typeDeclaration}) + + root = root.ReplaceNodes( + allProperties, + Function(original, updated) ReplaceNode(original, updated, backingFieldLookup, properties, model, Workspace)) + + Return root _ + .WithImport("System.Collections.Generic") _ + .WithImport("System.ComponentModel") + End Function + + Private Function ReplaceNode( + original As SyntaxNode, + updated As SyntaxNode, + backingFieldLookup As Dictionary(Of DeclarationStatementSyntax, String), + properties As IEnumerable(Of ExpandablePropertyInfo), + model As SemanticModel, + workspace As Workspace) As SyntaxNode + + Return If(TypeOf original Is TypeBlockSyntax, + ExpandType(DirectCast(original, TypeBlockSyntax), + DirectCast(updated, TypeBlockSyntax), + properties.Where(Function(p) p.NeedsBackingField), + model, + workspace), + DirectCast(ExpandProperty(DirectCast(original, DeclarationStatementSyntax), backingFieldLookup(DirectCast(original, DeclarationStatementSyntax))), SyntaxNode)) + End Function + + + Private Function WithImport(root As CompilationUnitSyntax, name As String) As CompilationUnitSyntax + If Not root.Imports _ + .SelectMany(Function(i) i.ImportsClauses) _ + .Any(Function(i) i.IsKind(SyntaxKind.SimpleImportsClause) AndAlso DirectCast(i, SimpleImportsClauseSyntax).Name.ToString() = name) Then + + Dim clause As ImportsClauseSyntax = SyntaxFactory.SimpleImportsClause(SyntaxFactory.ParseName(name).NormalizeWhitespace(elasticTrivia:=True)) + Dim clauseList = SyntaxFactory.SeparatedList({clause}) + Dim statement = SyntaxFactory.ImportsStatement(clauseList) + statement = statement.WithAdditionalAnnotations(Formatter.Annotation) + + root = root.AddImports(statement) + End If + + Return root + End Function + + Private Function ExpandProperty(propertyDeclaration As DeclarationStatementSyntax, backingFieldName As String) As SyntaxNode + Dim getter As AccessorBlockSyntax = Nothing + Dim setter As AccessorBlockSyntax = Nothing + Dim propertyStatement As PropertyStatementSyntax = Nothing + Dim propertyBlock As PropertyBlockSyntax = Nothing + + If propertyDeclaration.IsKind(SyntaxKind.PropertyStatement) Then + propertyStatement = DirectCast(propertyDeclaration, PropertyStatementSyntax) + ElseIf propertyDeclaration.IsKind(SyntaxKind.PropertyBlock) Then + propertyBlock = DirectCast(propertyDeclaration, PropertyBlockSyntax) + propertyStatement = propertyBlock.PropertyStatement + + If Not ExpansionChecker.TryGetAccessors(propertyBlock, getter, setter) Then + Throw New ArgumentException() + End If + Else + Debug.Fail("Unexpected declaration kind.") + End If + + If getter Is Nothing Then + getter = SyntaxFactory.AccessorBlock(SyntaxKind.GetAccessorBlock, + SyntaxFactory.AccessorStatement(SyntaxKind.GetAccessorStatement, SyntaxFactory.Token(SyntaxKind.GetKeyword)), + SyntaxFactory.EndBlockStatement(SyntaxKind.EndGetStatement, SyntaxFactory.Token(SyntaxKind.GetKeyword))) + End If + + Dim returnFieldStatement = SyntaxFactory.ParseExecutableStatement(String.Format("Return {0}", backingFieldName)) + getter = getter.WithStatements(SyntaxFactory.SingletonList(returnFieldStatement)) + + If setter Is Nothing Then + Dim propertyTypeText = DirectCast(propertyStatement.AsClause, SimpleAsClauseSyntax).Type.ToString() + Dim parameterList = SyntaxFactory.ParseParameterList(String.Format("(value As {0})", propertyTypeText)) + setter = SyntaxFactory.AccessorBlock(SyntaxKind.SetAccessorBlock, + SyntaxFactory.AccessorStatement(SyntaxKind.SetAccessorStatement, SyntaxFactory.Token(SyntaxKind.SetKeyword)). + WithParameterList(parameterList), + SyntaxFactory.EndBlockStatement(SyntaxKind.EndSetStatement, SyntaxFactory.Token(SyntaxKind.SetKeyword))) + End If + + Dim setPropertyStatement = SyntaxFactory.ParseExecutableStatement(String.Format("SetProperty({0}, value, ""{1}"")", backingFieldName, propertyStatement.Identifier.ValueText)) + setter = setter.WithStatements(SyntaxFactory.SingletonList(setPropertyStatement)) + + Dim newPropertyBlock As PropertyBlockSyntax = propertyBlock + If newPropertyBlock Is Nothing Then + newPropertyBlock = SyntaxFactory.PropertyBlock(propertyStatement, SyntaxFactory.List(Of AccessorBlockSyntax)()) + End If + + newPropertyBlock = newPropertyBlock.WithAccessors(SyntaxFactory.List({getter, setter})) + + Return newPropertyBlock + End Function + + Private Function ExpandType( + original As TypeBlockSyntax, + updated As TypeBlockSyntax, + properties As IEnumerable(Of ExpandablePropertyInfo), + model As SemanticModel, + workspace As Workspace) As TypeBlockSyntax + + Debug.Assert(original IsNot updated) + + Return updated _ + .WithBackingFields(properties, workspace) _ + .WithBaseType(original, model) _ + .WithPropertyChangedEvent(original, model, workspace) _ + .WithSetPropertyMethod(original, model, workspace) _ + .NormalizeWhitespace(elasticTrivia:=True) _ + .WithAdditionalAnnotations(Formatter.Annotation) + End Function + + + Private Function WithBackingFields(node As TypeBlockSyntax, properties As IEnumerable(Of ExpandablePropertyInfo), workspace As Workspace) As TypeBlockSyntax + + For Each propertyInfo In properties + Dim newField = GenerateBackingField(propertyInfo, workspace) + Dim currentProp = GetProperty(node, GetPropertyName(propertyInfo.PropertyDeclaration)) + node = node.InsertNodesBefore(currentProp, {newField}) + Next + + Return node + End Function + + Private Function GetPropertyName(node As DeclarationStatementSyntax) As String + Dim block = TryCast(node, PropertyBlockSyntax) + If block IsNot Nothing Then + Return block.PropertyStatement.Identifier.Text + End If + Dim prop = TryCast(node, PropertyStatementSyntax) + If prop IsNot Nothing Then + Return prop.Identifier.Text + End If + Return Nothing + End Function + + Private Function GetProperty(node As TypeBlockSyntax, name As String) As DeclarationStatementSyntax + Return node.DescendantNodes().OfType(Of DeclarationStatementSyntax).FirstOrDefault(Function(n) GetPropertyName(n) = name) + End Function + + Private Function GenerateBackingField(propertyInfo As ExpandablePropertyInfo, workspace As Workspace) As StatementSyntax + Dim g = SyntaxGenerator.GetGenerator(workspace, LanguageNames.VisualBasic) + Dim fieldType = g.TypeExpression(propertyInfo.Type) + + Dim fieldDecl = DirectCast(ParseMember(String.Format("Private {0} As _fieldType_", propertyInfo.BackingFieldName)), FieldDeclarationSyntax) + Return fieldDecl.ReplaceNode(fieldDecl.Declarators(0).AsClause.Type, fieldType).WithAdditionalAnnotations(Formatter.Annotation) + End Function + + Private Function ParseMember(source As String) As StatementSyntax + Dim cu = SyntaxFactory.ParseCompilationUnit("Class x" & vbCrLf & source & vbCrLf & "End Class") + Return DirectCast(cu.Members(0), ClassBlockSyntax).Members(0) + End Function + + + Private Function AddMembers(node As TypeBlockSyntax, ParamArray members As StatementSyntax()) As TypeBlockSyntax + Return AddMembers(node, DirectCast(members, IEnumerable(Of StatementSyntax))) + End Function + + + Private Function AddMembers(node As TypeBlockSyntax, members As IEnumerable(Of StatementSyntax)) As TypeBlockSyntax + Dim classBlock = TryCast(node, ClassBlockSyntax) + If classBlock IsNot Nothing Then + Return classBlock.WithMembers(classBlock.Members.AddRange(members)) + End If + + Dim structBlock = TryCast(node, StructureBlockSyntax) + If structBlock IsNot Nothing Then + Return structBlock.WithMembers(structBlock.Members.AddRange(members)) + End If + + Return node + End Function + + + Private Function WithBaseType(node As TypeBlockSyntax, original As TypeBlockSyntax, model As SemanticModel) As TypeBlockSyntax + Dim classSymbol = DirectCast(model.GetDeclaredSymbol(original), INamedTypeSymbol) + Dim interfaceSymbol = model.Compilation.GetTypeByMetadataName(InterfaceName) + + ' Does this class already implement INotifyPropertyChanged? If not, add it to the base list. + If Not classSymbol.AllInterfaces.Any(Function(i) i.Equals(interfaceSymbol)) Then + ' Add an annotation to simplify the name + Dim baseTypeName = SyntaxFactory.ParseTypeName(InterfaceName) _ + .WithAdditionalAnnotations(Simplifier.Annotation) + + ' Add an annotation to format properly. + Dim implementsStatement = SyntaxFactory.ImplementsStatement(baseTypeName). + WithAdditionalAnnotations(Formatter.Annotation) + + node = If(node.IsKind(SyntaxKind.ClassBlock), + DirectCast(DirectCast(node, ClassBlockSyntax).AddImplements(implementsStatement), TypeBlockSyntax), + DirectCast(node, StructureBlockSyntax).AddImplements(implementsStatement)) + End If + + Return node + End Function + + Private Const InterfaceName As String = "System.ComponentModel.INotifyPropertyChanged" + + + Private Function WithPropertyChangedEvent(node As TypeBlockSyntax, original As TypeBlockSyntax, model As SemanticModel, workspace As Workspace) As TypeBlockSyntax + Dim classSymbol = DirectCast(model.GetDeclaredSymbol(original), INamedTypeSymbol) + Dim interfaceSymbol = model.Compilation.GetTypeByMetadataName(InterfaceName) + Dim propertyChangedEventSymbol = DirectCast(interfaceSymbol.GetMembers("PropertyChanged").Single(), IEventSymbol) + Dim propertyChangedEvent = classSymbol.FindImplementationForInterfaceMember(propertyChangedEventSymbol) + + ' Does this class contain an implementation for the PropertyChanged event? If not, add it. + If propertyChangedEvent Is Nothing Then + node = AddMembers(node, GeneratePropertyChangedEvent()) + End If + + Return node + End Function + + Friend Function GeneratePropertyChangedEvent() As StatementSyntax + Dim decl = ParseMember("Public Event PropertyChanged As System.ComponentModel.PropertyChangedEventHandler Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged") + Return decl.WithAdditionalAnnotations(Simplifier.Annotation) + End Function + + + Private Function WithSetPropertyMethod(node As TypeBlockSyntax, original As TypeBlockSyntax, model As SemanticModel, workspace As Workspace) As TypeBlockSyntax + Dim classSymbol = DirectCast(model.GetDeclaredSymbol(original), INamedTypeSymbol) + Dim interfaceSymbol = model.Compilation.GetTypeByMetadataName(InterfaceName) + Dim propertyChangedEventSymbol = DirectCast(interfaceSymbol.GetMembers("PropertyChanged").Single(), IEventSymbol) + Dim propertyChangedEvent = classSymbol.FindImplementationForInterfaceMember(propertyChangedEventSymbol) + + Dim setPropertyMethod = classSymbol.FindSetPropertyMethod(model.Compilation) + If setPropertyMethod Is Nothing Then + node = AddMembers(node, GenerateSetPropertyMethod()) + End If + + Return node + End Function + + Friend Function GenerateSetPropertyMethod() As StatementSyntax + Return ParseMember( +Private Sub SetProperty(Of T)(ByRef field As T, value As T, name As String) + If Not EqualityComparer(Of T).Default.Equals(field, value) Then + field = value + RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(name)) + End If +End Sub +.Value).WithAdditionalAnnotations(Simplifier.Annotation) + + End Function + + + Private Function FindSetPropertyMethod(classSymbol As INamedTypeSymbol, compilation As Compilation) As IMethodSymbol + ' Find SetProperty(Of T)(ByRef T, T, string) method. + Dim setPropertyMethod = classSymbol. + GetMembers("SetProperty").OfType(Of IMethodSymbol)(). + FirstOrDefault(Function(m) m.Parameters.Count = 3 AndAlso m.TypeParameters.Count = 1) + + If setPropertyMethod IsNot Nothing Then + Dim parameters = setPropertyMethod.Parameters + Dim typeParameter = setPropertyMethod.TypeParameters(0) + + Dim stringType = compilation.GetSpecialType(SpecialType.System_String) + + If (setPropertyMethod.ReturnsVoid AndAlso + parameters(0).RefKind = RefKind.Ref AndAlso + parameters(0).Type.Equals(typeParameter) AndAlso + parameters(1).Type.Equals(typeParameter) AndAlso + parameters(2).Type.Equals(stringType)) Then + + Return setPropertyMethod + End If + End If + + Return Nothing + End Function +End Module diff --git a/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ExpandablePropertyInfo.vb b/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ExpandablePropertyInfo.vb new file mode 100644 index 000000000..9a87c06a6 --- /dev/null +++ b/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ExpandablePropertyInfo.vb @@ -0,0 +1,11 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Friend Class ExpandablePropertyInfo + Public Property BackingFieldName As String + Public Property NeedsBackingField As Boolean + Public Property PropertyDeclaration As DeclarationStatementSyntax + Public Property Type As ITypeSymbol +End Class diff --git a/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ExpansionChecker.vb b/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ExpansionChecker.vb new file mode 100644 index 000000000..7c7fc0ba6 --- /dev/null +++ b/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ExpansionChecker.vb @@ -0,0 +1,264 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.ComponentModel +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Friend Class ExpansionChecker + Friend Shared Function GetExpandableProperties(span As TextSpan, root As SyntaxNode, model As SemanticModel) As IEnumerable(Of ExpandablePropertyInfo) + Dim propertiesInTypes = root.DescendantNodes(span) _ + .OfType(Of PropertyStatementSyntax) _ + .Select(Function(p) GetExpandablePropertyInfo(p, model)) _ + .Where(Function(p) p IsNot Nothing) _ + .GroupBy(Function(p) p.PropertyDeclaration.FirstAncestorOrSelf(Of TypeBlockSyntax)) + + Return If(propertiesInTypes.Any(), + propertiesInTypes.First(), + Enumerable.Empty(Of ExpandablePropertyInfo)) + End Function + + ''' Returns true if the specified can be expanded to + ''' include support for . + Friend Shared Function GetExpandablePropertyInfo(propertyStatement As PropertyStatementSyntax, model As SemanticModel) As ExpandablePropertyInfo + If propertyStatement.ContainsDiagnostics Then + Return Nothing + End If + + If propertyStatement.Modifiers.Any(SyntaxKind.SharedKeyword) OrElse + propertyStatement.Modifiers.Any(SyntaxKind.MustOverrideKeyword) Then + Return Nothing + End If + + If propertyStatement.AsClause Is Nothing Then + Return Nothing + End If + + Dim propertyBlock = TryCast(propertyStatement.Parent, PropertyBlockSyntax) + If propertyBlock Is Nothing Then + ' We're an auto property, we can be expanded. + Return New ExpandablePropertyInfo With + { + .PropertyDeclaration = propertyStatement, + .BackingFieldName = GenerateFieldName(propertyStatement, model), + .NeedsBackingField = True, + .Type = model.GetDeclaredSymbol(propertyStatement).Type + } + End If + + ' Not an auto property, look more closely. + If propertyBlock.ContainsDiagnostics Then + Return Nothing + End If + + ' Only expand properties with both a getter and a setter. + Dim getter As AccessorBlockSyntax = Nothing + Dim setter As AccessorBlockSyntax = Nothing + If Not TryGetAccessors(propertyBlock, getter, setter) Then + Return Nothing + End If + + Dim backingField As IFieldSymbol = Nothing + Return If(IsExpandableGetter(getter, model, backingField) AndAlso IsExpandableSetter(setter, model, backingField), + New ExpandablePropertyInfo With {.PropertyDeclaration = propertyBlock, .BackingFieldName = backingField.Name}, + Nothing) + End Function + + ''' Retrieves the get and set accessor declarations of the specified property. + ''' Returns true if both get and set accessors exist; otherwise false. + Friend Shared Function TryGetAccessors(propertyBlock As PropertyBlockSyntax, + ByRef getter As AccessorBlockSyntax, + ByRef setter As AccessorBlockSyntax) As Boolean + Dim accessors = propertyBlock.Accessors + getter = accessors.FirstOrDefault(Function(ad) ad.AccessorStatement.Kind() = SyntaxKind.GetAccessorStatement) + setter = accessors.FirstOrDefault(Function(ad) ad.AccessorStatement.Kind() = SyntaxKind.SetAccessorStatement) + Return getter IsNot Nothing AndAlso setter IsNot Nothing + End Function + + Private Shared Function IsExpandableGetter(getter As AccessorBlockSyntax, + semanticModel As SemanticModel, + ByRef backingField As IFieldSymbol) As Boolean + backingField = GetBackingFieldFromGetter(getter, semanticModel) + Return backingField IsNot Nothing + End Function + + Private Shared Function GetBackingFieldFromGetter(getter As AccessorBlockSyntax, semanticModel As SemanticModel) As IFieldSymbol + If Not getter.Statements.Any() Then + Return Nothing + End If + + Dim statements = getter.Statements + If statements.Count <> 1 Then + Return Nothing + End If + + Dim returnStatement = TryCast(statements.Single(), ReturnStatementSyntax) + If returnStatement Is Nothing OrElse returnStatement.Expression Is Nothing Then + Return Nothing + End If + + Return TryCast(semanticModel.GetSymbolInfo(returnStatement.Expression).Symbol, IFieldSymbol) + End Function + + Private Shared Function GenerateFieldName(propertyStatement As PropertyStatementSyntax, semanticModel As SemanticModel) As String + Dim baseName = propertyStatement.Identifier.ValueText + baseName = "_" & Char.ToLower(baseName(0)) & baseName.Substring(1) + Dim propertySymbol = TryCast(semanticModel.GetDeclaredSymbol(propertyStatement), IPropertySymbol) + If propertySymbol Is Nothing OrElse propertySymbol.ContainingType Is Nothing Then + Return baseName + End If + + Dim index = 0 + Dim name = baseName + While DirectCast(propertySymbol.ContainingType, INamedTypeSymbol).MemberNames.Contains(name, StringComparer.OrdinalIgnoreCase) + name = baseName & index.ToString() + index += 1 + End While + + Return name + End Function + + Private Shared Function IsExpandableSetter(setter As AccessorBlockSyntax, + semanticModel As SemanticModel, + backingField As IFieldSymbol) As Boolean + Return IsExpandableSetterPattern1(setter, backingField, semanticModel) OrElse + IsExpandableSetterPattern2(setter, backingField, semanticModel) OrElse + IsExpandableSetterPattern3(setter, backingField, semanticModel) + End Function + + Private Shared Function IsExpandableSetterPattern1(setter As AccessorBlockSyntax, + backingField As IFieldSymbol, + semanticModel As SemanticModel) As Boolean + Dim statements = setter.Statements + If statements.Count <> 1 Then + Return False + End If + + Dim expressionStatement = statements.First() + Return IsAssignmentOfPropertyValueParameterToBackingField(expressionStatement, backingField, semanticModel) + End Function + + Private Shared Function IsExpandableSetterPattern2(setter As AccessorBlockSyntax, + backingField As IFieldSymbol, + semanticModel As SemanticModel) As Boolean + Dim statements = setter.Statements + If statements.Count <> 1 Then + Return False + End If + + Dim statement As StatementSyntax = Nothing + Dim condition As ExpressionSyntax = Nothing + + If Not GetConditionAndSingleStatementFromIfStatement(statements(0), statement, condition) Then + Return False + End If + + If Not IsAssignmentOfPropertyValueParameterToBackingField(statement, backingField, semanticModel) Then + Return False + End If + + If condition Is Nothing OrElse condition.Kind() <> SyntaxKind.NotEqualsExpression Then + Return False + End If + + Return ComparesPropertyValueParameterAndBackingField(DirectCast(condition, BinaryExpressionSyntax), + backingField, + semanticModel) + End Function + + Private Shared Function IsExpandableSetterPattern3(setter As AccessorBlockSyntax, + backingField As IFieldSymbol, + semanticModel As SemanticModel) As Boolean + Dim statements = setter.Statements + If statements.Count <> 2 Then + Return False + End If + + Dim statement As StatementSyntax = Nothing + Dim condition As ExpressionSyntax = Nothing + + If Not GetConditionAndSingleStatementFromIfStatement(statements(0), statement, condition) Then + Return False + End If + + Dim returnStatement = TryCast(statement, ReturnStatementSyntax) + If returnStatement Is Nothing OrElse returnStatement.Expression IsNot Nothing Then + Return False + End If + + If Not IsAssignmentOfPropertyValueParameterToBackingField(statements(1), backingField, semanticModel) Then + Return False + End If + + If condition.Kind() <> SyntaxKind.EqualsExpression Then + Return False + End If + + Return ComparesPropertyValueParameterAndBackingField(DirectCast(condition, BinaryExpressionSyntax), + backingField, + semanticModel) + End Function + + Private Shared Function IsAssignmentOfPropertyValueParameterToBackingField(statement As StatementSyntax, + backingField As IFieldSymbol, semanticModel As SemanticModel) As Boolean + If statement.Kind() <> SyntaxKind.SimpleAssignmentStatement Then + Return False + End If + + Dim assignment = DirectCast(statement, AssignmentStatementSyntax) + Return IsBackingField(assignment.Left, backingField, semanticModel) AndAlso IsPropertyValueParameter(assignment.Right, semanticModel) + End Function + + Private Shared Function GetConditionAndSingleStatementFromIfStatement(ifStatement As StatementSyntax, + ByRef statement As StatementSyntax, + ByRef condition As ExpressionSyntax) As Boolean + Dim multiLineIfStatement = TryCast(ifStatement, MultiLineIfBlockSyntax) + If multiLineIfStatement IsNot Nothing Then + If multiLineIfStatement.Statements.Count <> 1 Then + Return False + End If + + statement = multiLineIfStatement.Statements.First() + condition = multiLineIfStatement.IfStatement.Condition + Return True + Else + Dim singleLineIfStatement = TryCast(ifStatement, SingleLineIfStatementSyntax) + If singleLineIfStatement IsNot Nothing Then + If singleLineIfStatement.Statements.Count <> 1 Then + Return False + End If + + statement = singleLineIfStatement.Statements.First() + condition = singleLineIfStatement.Condition + Return True + End If + Return False + End If + End Function + + Private Shared Function IsBackingField(expression As ExpressionSyntax, + backingField As IFieldSymbol, + semanticModel As SemanticModel) As Boolean + Return Object.Equals(semanticModel.GetSymbolInfo(expression).Symbol, backingField) + End Function + + Private Shared Function IsPropertyValueParameter(expression As ExpressionSyntax, + semanticModel As SemanticModel) As Boolean + Dim symbol = semanticModel.GetSymbolInfo(expression).Symbol + + Return symbol IsNot Nothing AndAlso + symbol.Kind = SymbolKind.Parameter AndAlso + symbol.ContainingSymbol.Kind = SymbolKind.Method AndAlso + DirectCast(symbol.ContainingSymbol, IMethodSymbol).MethodKind = MethodKind.PropertySet + End Function + + Private Shared Function ComparesPropertyValueParameterAndBackingField(expression As BinaryExpressionSyntax, + backingField As IFieldSymbol, + semanticModel As SemanticModel) As Boolean + + Return (IsPropertyValueParameter(expression.Right, semanticModel) AndAlso IsBackingField(expression.Left, backingField, semanticModel)) OrElse + (IsPropertyValueParameter(expression.Left, semanticModel) AndAlso IsBackingField(expression.Right, backingField, semanticModel)) + End Function + +End Class diff --git a/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.VisualBasic.vbproj b/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.VisualBasic.vbproj new file mode 100644 index 000000000..3a1a5c970 --- /dev/null +++ b/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged.VisualBasic.vbproj @@ -0,0 +1,12 @@ + + + + netstandard1.3 + + + + + + + + diff --git a/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChangedCodeRefactoringProvider.vb b/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChangedCodeRefactoringProvider.vb new file mode 100644 index 000000000..8251e4b57 --- /dev/null +++ b/samples/VisualBasic/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChanged/ImplementNotifyPropertyChangedCodeRefactoringProvider.vb @@ -0,0 +1,52 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Composition +Imports System.Threading +Imports System.Threading.Tasks +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeActions +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Formatting +Imports Microsoft.CodeAnalysis.Simplification +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + + +Friend Class ImplementNotifyPropertyChangedCodeRefactoringProvider + Inherits CodeRefactoringProvider + + Public NotOverridable Overrides Async Function ComputeRefactoringsAsync(context As CodeRefactoringContext) As Task + Dim document = context.Document + Dim textSpan = context.Span + Dim cancellationToken = context.CancellationToken + + Dim root = DirectCast(Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False), CompilationUnitSyntax) + Dim model = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False) + + ' if length Is 0 then no particular range Is selected, so pick the first enclosing declaration + If textSpan.Length = 0 Then + Dim decl = root.FindToken(textSpan.Start).Parent.AncestorsAndSelf().OfType(Of DeclarationStatementSyntax)().FirstOrDefault() + If decl IsNot Nothing Then + textSpan = decl.Span + End If + End If + + Dim properties = ExpansionChecker.GetExpandableProperties(textSpan, root, model) + + If properties.Any Then + Dim action = CodeAction.Create( + "Apply INotifyPropertyChanged pattern", + Function(c) ImplementNotifyPropertyChangedAsync(document, root, model, properties, c), + equivalenceKey:=NameOf(ImplementNotifyPropertyChangedCodeRefactoringProvider)) + + context.RegisterRefactoring(action) + End If + End Function + + Private Async Function ImplementNotifyPropertyChangedAsync(document As Document, root As CompilationUnitSyntax, model As SemanticModel, properties As IEnumerable(Of ExpandablePropertyInfo), cancellationToken As CancellationToken) As Task(Of Document) + document = document.WithSyntaxRoot(CodeGeneration.ImplementINotifyPropertyChanged(root, model, properties, document.Project.Solution.Workspace)) + document = Await Simplifier.ReduceAsync(document, Simplifier.Annotation, cancellationToken:=cancellationToken).ConfigureAwait(False) + document = Await Formatter.FormatAsync(document, Formatter.Annotation, cancellationToken:=cancellationToken).ConfigureAwait(False) + Return document + End Function +End Class diff --git a/samples/VisualBasic/MakeConst/MakeConst.Test/MakeConst.VisualBasic.UnitTests.vbproj b/samples/VisualBasic/MakeConst/MakeConst.Test/MakeConst.VisualBasic.UnitTests.vbproj new file mode 100644 index 000000000..a14a60744 --- /dev/null +++ b/samples/VisualBasic/MakeConst/MakeConst.Test/MakeConst.VisualBasic.UnitTests.vbproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.0 + + true + true + + + + + + + + + + + + + diff --git a/samples/VisualBasic/MakeConst/MakeConst.Test/MakeConstUnitTests.vb b/samples/VisualBasic/MakeConst/MakeConst.Test/MakeConstUnitTests.vb new file mode 100644 index 000000000..b4cf34905 --- /dev/null +++ b/samples/VisualBasic/MakeConst/MakeConst.Test/MakeConstUnitTests.vb @@ -0,0 +1,22 @@ +Imports Xunit +Imports Verify = Microsoft.CodeAnalysis.VisualBasic.Testing.XUnit.CodeFixVerifier(Of MakeConst.VisualBasic.MakeConstAnalyzer, MakeConst.VisualBasic.MakeConstCodeFixProvider) + +Namespace MakeConst.Test + Public Class UnitTest + + 'No diagnostics expected to show up + + Public Async Function TestMethod1() As Task + Dim test = "" + Await Verify.VerifyAnalyzerAsync(test) + End Function + + 'Diagnostic And CodeFix both triggered And checked for + + Public Sub TestMethod2() + + + End Sub + + End Class +End Namespace diff --git a/samples/VisualBasic/MakeConst/MakeConst.Vsix/MakeConst.VisualBasic.Vsix.vbproj b/samples/VisualBasic/MakeConst/MakeConst.Vsix/MakeConst.VisualBasic.Vsix.vbproj new file mode 100644 index 000000000..f01269be5 --- /dev/null +++ b/samples/VisualBasic/MakeConst/MakeConst.Vsix/MakeConst.VisualBasic.Vsix.vbproj @@ -0,0 +1,22 @@ + + + + net472 + MakeConst.VisualBasic.Vsix + MakeConst.VisualBasic + false + false + false + false + false + + + + + Designer + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/MakeConst/MakeConst.Vsix/My Project/AssemblyInfo.vb b/samples/VisualBasic/MakeConst/MakeConst.Vsix/My Project/AssemblyInfo.vb new file mode 100644 index 000000000..6d61af6aa --- /dev/null +++ b/samples/VisualBasic/MakeConst/MakeConst.Vsix/My Project/AssemblyInfo.vb @@ -0,0 +1,32 @@ +Imports System +Imports System.Reflection +Imports System.Runtime.InteropServices + +' General Information about an assembly is controlled through the following +' set of attributes. Change these attribute values to modify the information +' associated with an assembly. + +' Review the values of the assembly attributes + + + + + + + + + + +' Version information for an assembly consists of the following four values: +' +' Major Version +' Minor Version +' Build Number +' Revision +' +' You can specify all the values or you can default the Build and Revision Numbers +' by using the '*' as shown below: +' + + + diff --git a/samples/VisualBasic/MakeConst/MakeConst.Vsix/source.extension.vsixmanifest b/samples/VisualBasic/MakeConst/MakeConst.Vsix/source.extension.vsixmanifest new file mode 100644 index 000000000..c62080349 --- /dev/null +++ b/samples/VisualBasic/MakeConst/MakeConst.Vsix/source.extension.vsixmanifest @@ -0,0 +1,22 @@ + + + + + Make Const for VB + This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn"). + + + + + + + + + + + + + + + + diff --git a/samples/VisualBasic/MakeConst/MakeConst/MakeConst.VisualBasic.vbproj b/samples/VisualBasic/MakeConst/MakeConst/MakeConst.VisualBasic.vbproj new file mode 100644 index 000000000..34bb72873 --- /dev/null +++ b/samples/VisualBasic/MakeConst/MakeConst/MakeConst.VisualBasic.vbproj @@ -0,0 +1,40 @@ + + + + netstandard1.3 + false + True + + + + Roslyn.VB.Sample.MakeConst + 1.0.0.0 + Microsoft + http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE + http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE + http://ICON_URL_HERE_OR_DELETE_THIS_LINE + http://REPOSITORY_URL_HERE_OR_DELETE_THIS_LINE + false + MakeConst + Summary of changes made in this release of the package. + Copyright + MakeConst, analyzers + true + + + + + + + + + + + + + + + + + + diff --git a/samples/VisualBasic/MakeConst/MakeConst/MakeConstAnalyzer.vb b/samples/VisualBasic/MakeConst/MakeConst/MakeConstAnalyzer.vb new file mode 100644 index 000000000..1ac5725d2 --- /dev/null +++ b/samples/VisualBasic/MakeConst/MakeConst/MakeConstAnalyzer.vb @@ -0,0 +1,72 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + + +Public Class MakeConstAnalyzer + ' Implementing syntax node analyzer because the make const diagnostics in one method body are not dependent on the contents of other method bodies. + Inherits DiagnosticAnalyzer + + Public Const MakeConstDiagnosticId As String = "MakeConstVB" + Public Shared ReadOnly MakeConstRule As New DiagnosticDescriptor(MakeConstDiagnosticId, + "Make Const", + "Can be made const", + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault:=True) + + Public Overrides Sub Initialize(context As AnalysisContext) + context.RegisterSyntaxNodeAction(AddressOf AnalyzeNode, SyntaxKind.LocalDeclarationStatement) + End Sub + + Public Overrides ReadOnly Property SupportedDiagnostics As ImmutableArray(Of DiagnosticDescriptor) + Get + Return ImmutableArray.Create(MakeConstRule) + End Get + End Property + + Private Function CanBeMadeConst(localDeclaration As LocalDeclarationStatementSyntax, semanticModel As SemanticModel) As Boolean + ' Only consider local variable declarations that are Dim (no Static or Const). + If Not localDeclaration.Modifiers.All(Function(m) m.Kind() = SyntaxKind.DimKeyword) Then + Return False + End If + + ' Ensure that all variable declarators in the local declaration have + ' initializers and a single variable name. Additionally, ensure that + ' each variable is assigned with a constant value. + For Each declarator In localDeclaration.Declarators + If declarator.Initializer Is Nothing OrElse declarator.Names.Count <> 1 Then + Return False + End If + + If Not semanticModel.GetConstantValue(declarator.Initializer.Value).HasValue Then + Return False + End If + Next + + ' Perform data flow analysis on the local declaration. + Dim dataFlowAnalysis = semanticModel.AnalyzeDataFlow(localDeclaration) + + ' Retrieve the local symbol for each variable in the local declaration + ' and ensure that it is not written outside the data flow analysis region. + For Each declarator In localDeclaration.Declarators + Dim variable = declarator.Names.Single() + Dim variableSymbol = semanticModel.GetDeclaredSymbol(variable) + If dataFlowAnalysis.WrittenOutside.Contains(variableSymbol) Then + Return False + End If + Next + + Return True + End Function + + Private Sub AnalyzeNode(context As SyntaxNodeAnalysisContext) + If CanBeMadeConst(CType(context.Node, LocalDeclarationStatementSyntax), context.SemanticModel) Then + context.ReportDiagnostic(Diagnostic.Create(MakeConstRule, context.Node.GetLocation())) + End If + End Sub +End Class diff --git a/samples/VisualBasic/MakeConst/MakeConst/MakeConstCodeFixProvider.vb b/samples/VisualBasic/MakeConst/MakeConst/MakeConstCodeFixProvider.vb new file mode 100644 index 000000000..3c47cdf00 --- /dev/null +++ b/samples/VisualBasic/MakeConst/MakeConst/MakeConstCodeFixProvider.vb @@ -0,0 +1,68 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports System.Composition +Imports System.Threading +Imports System.Threading.Tasks +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeActions +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Formatting +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + + +Public NotInheritable Class MakeConstCodeFixProvider + Inherits CodeFixProvider + + Public Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) + Get + Return ImmutableArray.Create(MakeConstAnalyzer.MakeConstDiagnosticId) + End Get + End Property + + Public Overrides Function GetFixAllProvider() As FixAllProvider + ' See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers + Return WellKnownFixAllProviders.BatchFixer + End Function + + Public Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task + Dim diagnostic = context.Diagnostics.First() + Dim diagnosticSpan = diagnostic.Location.SourceSpan + Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + + ' Find the local declaration identified by the diagnostic. + Dim declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType(Of LocalDeclarationStatementSyntax)().First() + + ' Register a code action that will invoke the fix. + Dim action = CodeAction.Create( + "Make constant", + Function(c) MakeConstAsync(context.Document, declaration, c), + equivalenceKey:=NameOf(MakeConstCodeFixProvider)) + + context.RegisterCodeFix(action, diagnostic) + End Function + + Private Shared Async Function MakeConstAsync(document As Document, localDeclaration As LocalDeclarationStatementSyntax, cancellationToken As CancellationToken) As Task(Of Document) + ' Create a const token with the leading trivia from the local declaration. + Dim firstToken = localDeclaration.GetFirstToken() + Dim constToken = SyntaxFactory.Token( + firstToken.LeadingTrivia, SyntaxKind.ConstKeyword, firstToken.TrailingTrivia) + + ' Create a new modifier list with the const token. + Dim newModifiers = SyntaxFactory.TokenList(constToken) + + ' Produce new local declaration. + Dim newLocalDeclaration = localDeclaration.WithModifiers(newModifiers) + + ' Add an annotation to format the new local declaration. + Dim formattedLocalDeclaration = newLocalDeclaration.WithAdditionalAnnotations(Formatter.Annotation) + + ' Replace the old local declaration with the new local declaration. + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim newRoot = root.ReplaceNode(localDeclaration, formattedLocalDeclaration) + + ' Return document with transformed tree. + Return document.WithSyntaxRoot(newRoot) + End Function +End Class diff --git a/samples/VisualBasic/MakeConst/MakeConst/My Project/Resources.Designer.vb b/samples/VisualBasic/MakeConst/MakeConst/My Project/Resources.Designer.vb new file mode 100644 index 000000000..1863522de --- /dev/null +++ b/samples/VisualBasic/MakeConst/MakeConst/My Project/Resources.Designer.vb @@ -0,0 +1,91 @@ +'------------------------------------------------------------------------------ +' +' This code was generated by a tool. +' Runtime Version:4.0.30319.0 +' +' Changes to this file may cause incorrect behavior and will be lost if +' the code is regenerated. +' +'------------------------------------------------------------------------------ + +Option Strict On +Option Explicit On + +Imports System +Imports System.Reflection + +Namespace My.Resources + + 'This class was auto-generated by the StronglyTypedResourceBuilder + 'class via a tool like ResGen or Visual Studio. + 'To add or remove a member, edit your .ResX file then rerun ResGen + 'with the /str option, or rebuild your VS project. + ''' + ''' A strongly-typed resource class, for looking up localized strings, etc. + ''' + + Friend Module Resources + + Private resourceMan As Global.System.Resources.ResourceManager + + Private resourceCulture As Global.System.Globalization.CultureInfo + + ''' + ''' Returns the cached ResourceManager instance used by this class. + ''' + + Friend ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager + Get + If Object.ReferenceEquals(resourceMan, Nothing) Then + Dim temp As Global.System.Resources.ResourceManager = New Global.System.Resources.ResourceManager("MakeConst.Resources", GetType(Resources).GetTypeInfo.Assembly) + resourceMan = temp + End If + Return resourceMan + End Get + End Property + + ''' + ''' Overrides the current thread's CurrentUICulture property for all + ''' resource lookups using this strongly typed resource class. + ''' + + Friend Property Culture() As Global.System.Globalization.CultureInfo + Get + Return resourceCulture + End Get + Set + resourceCulture = Value + End Set + End Property + + ''' + ''' Looks up a localized string similar to Type name '{0}' contains lowercase letters. + ''' + Friend ReadOnly Property AnalyzerMessageFormat() As String + Get + Return ResourceManager.GetString("AnalyzerMessageFormat", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Type name contains lowercase letters. + ''' + Friend ReadOnly Property AnalyzerTitle() As String + Get + Return ResourceManager.GetString("AnalyzerTitle", resourceCulture) + End Get + End Property + + ''' + ''' Looks up a localized string similar to Type names should be all uppercase. + ''' + Friend ReadOnly Property AnalyzerDescription() As String + Get + Return ResourceManager.GetString("AnalyzerDescription", resourceCulture) + End Get + End Property + End Module +End Namespace diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Resources.resx b/samples/VisualBasic/MakeConst/MakeConst/My Project/Resources.resx similarity index 100% rename from src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Resources.resx rename to samples/VisualBasic/MakeConst/MakeConst/My Project/Resources.resx diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/install.ps1 b/samples/VisualBasic/MakeConst/MakeConst/tools/install.ps1 similarity index 100% rename from src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/install.ps1 rename to samples/VisualBasic/MakeConst/MakeConst/tools/install.ps1 diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/uninstall.ps1 b/samples/VisualBasic/MakeConst/MakeConst/tools/uninstall.ps1 similarity index 100% rename from src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/uninstall.ps1 rename to samples/VisualBasic/MakeConst/MakeConst/tools/uninstall.ps1 diff --git a/samples/VisualBasic/RemoveByVal/RemoveByVal.Vsix/My Project/AssemblyInfo.vb b/samples/VisualBasic/RemoveByVal/RemoveByVal.Vsix/My Project/AssemblyInfo.vb new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/samples/VisualBasic/RemoveByVal/RemoveByVal.Vsix/My Project/AssemblyInfo.vb @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/samples/VisualBasic/RemoveByVal/RemoveByVal.Vsix/RemoveByVal.VisualBasic.Vsix.vbproj b/samples/VisualBasic/RemoveByVal/RemoveByVal.Vsix/RemoveByVal.VisualBasic.Vsix.vbproj new file mode 100644 index 000000000..abf0abc26 --- /dev/null +++ b/samples/VisualBasic/RemoveByVal/RemoveByVal.Vsix/RemoveByVal.VisualBasic.Vsix.vbproj @@ -0,0 +1,22 @@ + + + + net472 + RemoveByVal.VisualBasic.Vsix + RemoveByVal.VisualBasic.Vsix + false + false + false + false + false + + + + + Designer + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/RemoveByVal/RemoveByVal.Vsix/source.extension.vsixmanifest b/samples/VisualBasic/RemoveByVal/RemoveByVal.Vsix/source.extension.vsixmanifest new file mode 100644 index 000000000..c51a865fd --- /dev/null +++ b/samples/VisualBasic/RemoveByVal/RemoveByVal.Vsix/source.extension.vsixmanifest @@ -0,0 +1,21 @@ + + + + + Remove ByVal for Visual Basic + This is a sample code refactoring extension for the .NET Compiler Platform ("Roslyn"). + + + + + + + + + + + + + + + diff --git a/samples/VisualBasic/RemoveByVal/RemoveByVal/CodeRefactoringProvider.vb b/samples/VisualBasic/RemoveByVal/RemoveByVal/CodeRefactoringProvider.vb new file mode 100644 index 000000000..8c4eb04c9 --- /dev/null +++ b/samples/VisualBasic/RemoveByVal/RemoveByVal/CodeRefactoringProvider.vb @@ -0,0 +1,87 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Option Strict Off + +Imports System.Threading +Imports System.Threading.Tasks +Imports System.Composition +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeActions +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic + + +Class RemoveByValCodeRefactoringProvider + Inherits CodeRefactoringProvider + + Public NotOverridable Overrides Async Function ComputeRefactoringsAsync(context As CodeRefactoringContext) As Task + Dim document = context.Document + Dim textSpan = context.Span + Dim cancellationToken = context.CancellationToken + + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim token = root.FindToken(textSpan.Start) + + If token.Kind = SyntaxKind.ByValKeyword AndAlso token.Span.IntersectsWith(textSpan.Start) Then + context.RegisterRefactoring(New RemoveByValCodeAction("Remove unnecessary ByVal keyword", + Function(c) RemoveOccuranceAsync(document, token, c))) + context.RegisterRefactoring(New RemoveByValCodeAction("Remove all occurrences", + Function(c) RemoveAllOccurancesAsync(document, c))) + End If + End Function + + Private Async Function RemoveOccuranceAsync(document As Document, token As SyntaxToken, cancellationToken As CancellationToken) As Task(Of Document) + Dim oldRoot = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim rewriter = New Rewriter(Function(t) t = token) + Dim newRoot = rewriter.Visit(oldRoot) + Return document.WithSyntaxRoot(newRoot) + End Function + + Private Async Function RemoveAllOccurancesAsync(document As Document, cancellationToken As CancellationToken) As Task(Of Document) + Dim oldRoot = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim rewriter = New Rewriter(Function(current) True) + Dim newRoot = rewriter.Visit(oldRoot) + Return document.WithSyntaxRoot(newRoot) + End Function + + Class Rewriter + Inherits VisualBasicSyntaxRewriter + + Private ReadOnly _predicate As Func(Of SyntaxToken, Boolean) + + Public Sub New(predicate As Func(Of SyntaxToken, Boolean)) + _predicate = predicate + End Sub + + Public Overrides Function VisitToken(token As SyntaxToken) As SyntaxToken + If token.Kind = SyntaxKind.ByValKeyword AndAlso _predicate(token) Then + Return SyntaxFactory.Token(token.LeadingTrivia, SyntaxKind.ByValKeyword, Nothing, String.Empty) + End If + + Return token + End Function + End Class + + Class RemoveByValCodeAction + Inherits CodeAction + + Private ReadOnly createChangedDocument As Func(Of Object, Task(Of Document)) + Private ReadOnly _title As String + + Public Sub New(title As String, createChangedDocument As Func(Of Object, Task(Of Document))) + _title = title + Me.createChangedDocument = createChangedDocument + End Sub + + Public Overrides ReadOnly Property Title As String + Get + Throw New NotImplementedException() + End Get + End Property + + Protected Overrides Function GetChangedDocumentAsync(cancellationToken As CancellationToken) As Task(Of Document) + Return createChangedDocument(cancellationToken) + End Function + End Class +End Class diff --git a/samples/VisualBasic/RemoveByVal/RemoveByVal/RemoveByVal.VisualBasic.vbproj b/samples/VisualBasic/RemoveByVal/RemoveByVal/RemoveByVal.VisualBasic.vbproj new file mode 100644 index 000000000..3a1a5c970 --- /dev/null +++ b/samples/VisualBasic/RemoveByVal/RemoveByVal/RemoveByVal.VisualBasic.vbproj @@ -0,0 +1,12 @@ + + + + netstandard1.3 + + + + + + + + diff --git a/samples/VisualBasic/SourceGenerators/GeneratedDemo/Cars.csv b/samples/VisualBasic/SourceGenerators/GeneratedDemo/Cars.csv new file mode 100644 index 000000000..26a5d2051 --- /dev/null +++ b/samples/VisualBasic/SourceGenerators/GeneratedDemo/Cars.csv @@ -0,0 +1,4 @@ +Brand, Model, Year, cc, Favorite +Fiat, Punto, 2008, 12.3, No +Ford, Wagon, 1956, 20.3, No +BMW, "335", 2014, 20.3, Yes \ No newline at end of file diff --git a/samples/VisualBasic/SourceGenerators/GeneratedDemo/MainSettings.xmlsettings b/samples/VisualBasic/SourceGenerators/GeneratedDemo/MainSettings.xmlsettings new file mode 100644 index 000000000..96b96f5e5 --- /dev/null +++ b/samples/VisualBasic/SourceGenerators/GeneratedDemo/MainSettings.xmlsettings @@ -0,0 +1,6 @@ + + + False + 1234 + Hello World! + \ No newline at end of file diff --git a/samples/VisualBasic/SourceGenerators/GeneratedDemo/People.csv b/samples/VisualBasic/SourceGenerators/GeneratedDemo/People.csv new file mode 100644 index 000000000..5179c3539 --- /dev/null +++ b/samples/VisualBasic/SourceGenerators/GeneratedDemo/People.csv @@ -0,0 +1,3 @@ +Name, address, 11Age +"Luca Bol", "23 Bell Street", 90 +"john doe", "32 Carl street", 45 \ No newline at end of file diff --git a/samples/VisualBasic/SourceGenerators/GeneratedDemo/Program.vb b/samples/VisualBasic/SourceGenerators/GeneratedDemo/Program.vb new file mode 100644 index 000000000..fd209d06b --- /dev/null +++ b/samples/VisualBasic/SourceGenerators/GeneratedDemo/Program.vb @@ -0,0 +1,33 @@ +Option Explicit On +Option Strict On +Option Infer On + +Module Program + + Public Sub Main() + + Console.WriteLine("Running HelloWorld: +") + UseHelloWorldGenerator.Run() + + Console.WriteLine(" + +Running AutoNotify: +") + UseAutoNotifyGenerator.Run() + + Console.WriteLine(" + +Running XmlSettings: +") + UseXmlSettingsGenerator.Run() + + Console.WriteLine(" + +Running CsvGenerator: +") + UseCsvGenerator.Run() + + End Sub + +End Module diff --git a/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.vb b/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.vb new file mode 100644 index 000000000..f810bd4a6 --- /dev/null +++ b/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.vb @@ -0,0 +1,42 @@ +Option Explicit On +Option Strict On +Option Infer On + +Imports AutoNotify + +' The view model we'd like to augment +Partial Public Class ExampleViewModel + + + Private _text As String = "private field text" + + + Private _amount As Integer = 5 + +End Class + +Public Module UseAutoNotifyGenerator + + Public Sub Run() + + Dim vm As New ExampleViewModel() + + ' we didn't explicitly create the 'Text' property, it was generated for us + Dim text = vm.Text + Console.WriteLine($"Text = {text}") + + ' Properties can have differnt names generated based on the PropertyName argument of the attribute + Dim count = vm.Count + Console.WriteLine($"Count = {count}") + + ' the viewmodel will automatically implement INotifyPropertyChanged + AddHandler vm.PropertyChanged, Sub(o, e) Console.WriteLine($"Property {e.PropertyName} was changed") + vm.Text = "abc" + vm.Count = 123 + + ' Try adding fields to the ExampleViewModel class above and tagging them with the attribute + ' You'll see the matching generated properties visibile in IntelliSense in realtime + + End Sub + +End Module diff --git a/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseCsvGenerator.vb b/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseCsvGenerator.vb new file mode 100644 index 000000000..f4c055835 --- /dev/null +++ b/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseCsvGenerator.vb @@ -0,0 +1,18 @@ +Option Explicit On +Option Strict On +Option Infer On + +Imports CSV + +Friend Class UseCsvGenerator + + Public Shared Sub Run() + + Console.WriteLine("## CARS") + Cars.All.ToList().ForEach(Sub(c) Console.WriteLine(c.Brand & vbTab & c.Model & vbTab & c.Year & vbTab & c.Cc & vbTab & c.Favorite)) + Console.WriteLine(vbCr & "## PEOPLE") + People.All.ToList().ForEach(Sub(p) Console.WriteLine(p.Name & vbTab & p.Address & vbTab & p._11Age)) + + End Sub + +End Class diff --git a/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.vb b/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.vb new file mode 100644 index 000000000..89fa11b47 --- /dev/null +++ b/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.vb @@ -0,0 +1,8 @@ +Public Module UseHelloWorldGenerator + + Public Sub Run() + ' The static call below is generated at build time, and will list the syntax trees used in the compilation + HelloWorldGenerated.HelloWorld.SayHello() + End Sub + +End Module diff --git a/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.vb b/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.vb new file mode 100644 index 000000000..eb9a8e730 --- /dev/null +++ b/samples/VisualBasic/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.vb @@ -0,0 +1,25 @@ +Imports AutoSettings + +Public Module UseXmlSettingsGenerator + + Public Sub Run() + + ' This XmlSettings generator makes a static property in the XmlSettings class for each .xmlsettings file + + ' here we have the 'Main' settings file from MainSettings.xmlsettings + ' the name is determined by the 'name' attribute of the root settings element + Dim main As XmlSettings.MainSettings = XmlSettings.Main + Console.WriteLine($"Reading settings from {main.GetLocation()}") + + ' settings are strongly typed and can be read directly from the static instance + Dim firstRun As Boolean = XmlSettings.Main.FirstRun + Console.WriteLine($"Setting firstRun = {firstRun}") + + Dim cacheSize As Integer = XmlSettings.Main.CacheSize + Console.WriteLine($"Setting cacheSize = {cacheSize}") + + ' Try adding some keys to the settings file and see the settings become available to read from + + End Sub + +End Module diff --git a/samples/VisualBasic/SourceGenerators/GeneratedDemo/VisualBasicGeneratedDemo.vbproj b/samples/VisualBasic/SourceGenerators/GeneratedDemo/VisualBasicGeneratedDemo.vbproj new file mode 100644 index 000000000..6df6a390c --- /dev/null +++ b/samples/VisualBasic/SourceGenerators/GeneratedDemo/VisualBasicGeneratedDemo.vbproj @@ -0,0 +1,23 @@ + + + + Exe + netcoreapp3.1 + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/SourceGenerators/README.md b/samples/VisualBasic/SourceGenerators/README.md new file mode 100644 index 000000000..63d0029eb --- /dev/null +++ b/samples/VisualBasic/SourceGenerators/README.md @@ -0,0 +1,35 @@ +🚧 Work In Progress +======== + +These samples are for an in-progress feature of Roslyn. As such they may change or break as the feature is developed, and no level of support is implied. + +For more information on the Source Generators feature, see the [design document](https://github.com/dotnet/roslyn/blob/master/docs/features/source-generators.md). + +Prerequisites +----- + +These samples require **Visual Studio 16.9.0 Preview 2.0** or higher. + +Building the samples +----- +Open `SourceGenerators.sln` in Visual Studio or run `dotnet build` from the `\SourceGenerators` directory. + +Running the samples +----- + +The generators must be run as part of another build, as they inject source into the project being built. This repo contains a sample project `GeneratorDemo` that relies of the sample generators to add code to it's compilation. + +Run `GeneratedDemo` in Visual studio or run `dotnet run` from the `GeneratorDemo` directory. + +Using the samples in your project +----- + +You can add the sample generators to your own project by adding an item group containing an analyzer reference: + +```xml + + + +``` + +You will most likely need to close and reopen the solution in Visual Studio for any changes made to the generators to take effect. diff --git a/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.vb b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.vb new file mode 100644 index 000000000..4c1ca4af8 --- /dev/null +++ b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.vb @@ -0,0 +1,201 @@ +Option Explicit On +Option Infer On +Option Strict On + +Imports System.Text + +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace SourceGeneratorSamples + + + Public Class AutoNotifyGenerator + Implements ISourceGenerator + + Private Const ATTRIBUTE_TEXT As String = " +Imports System + +Namespace Global.AutoNotify + + Friend NotInheritable Class AutoNotifyAttribute + Inherits Attribute + + Public Sub New() + End Sub + + Public Property PropertyName As String + End Class +End Namespace +" + + Public Sub Initialize(context As GeneratorInitializationContext) Implements ISourceGenerator.Initialize + ' Register a syntax receiver that will be created for each generation pass + context.RegisterForSyntaxNotifications(Function() As ISyntaxReceiver + Return New SyntaxReceiver + End Function) + End Sub + + Public Sub Execute(context As GeneratorExecutionContext) Implements ISourceGenerator.Execute + + ' add the attribute text + context.AddSource("AutoNotifyAttribute", SourceText.From(ATTRIBUTE_TEXT, Encoding.UTF8)) + + ' retreive the populated receiver + Dim tempVar = TypeOf context.SyntaxReceiver Is SyntaxReceiver + Dim receiver = TryCast(context.SyntaxReceiver, SyntaxReceiver) + If Not tempVar Then + Return + End If + + ' we're going to create a new compilation that contains the attribute. + ' TODO: we should allow source generators to provide source during initialize, so that this step isn't required. + Dim options1 = context.Compilation.SyntaxTrees.First().Options + Dim compilation1 = context.Compilation.AddSyntaxTrees(VisualBasicSyntaxTree.ParseText(SourceText.From(ATTRIBUTE_TEXT, Encoding.UTF8), CType(options1, VisualBasicParseOptions))) + + ' get the newly bound attribute, and INotifyPropertyChanged + Dim attributeSymbol = compilation1.GetTypeByMetadataName("AutoNotify.AutoNotifyAttribute") + Dim notifySymbol = compilation1.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged") + + ' loop over the candidate fields, and keep the ones that are actually annotated + Dim fieldSymbols As New List(Of IFieldSymbol) + + For Each field In receiver.CandidateFields + Dim model = compilation1.GetSemanticModel(field.SyntaxTree) + For Each variable In field.Declarators + For Each name In variable.Names + ' Get the symbol being decleared by the field, and keep it if its annotated + Dim fieldSymbol = TryCast(model.GetDeclaredSymbol(name), IFieldSymbol) + If fieldSymbol.GetAttributes().Any(Function(ad) ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.[Default])) Then + fieldSymbols.Add(fieldSymbol) + End If + Next + Next + Next + + ' group the fields by class, and generate the source + For Each group In fieldSymbols.GroupBy(Function(f) f.ContainingType, SymbolEqualityComparer.Default) + Dim classSource = ProcessClass(CType(group.Key, INamedTypeSymbol), group.ToList(), attributeSymbol, notifySymbol) + context.AddSource($"{group.Key.Name}_AutoNotify.vb", SourceText.From(classSource, Encoding.UTF8)) + Next + + End Sub + + Private Function ProcessClass(classSymbol As INamedTypeSymbol, fields As List(Of IFieldSymbol), attributeSymbol As ISymbol, notifySymbol As ISymbol) As String + + If Not classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.[Default]) Then + Return Nothing 'TODO: issue a diagnostic that it must be top level + End If + + Dim namespaceName = classSymbol.ContainingNamespace.ToDisplayString() + + ' begin building the generated source + Dim source = New StringBuilder($"Option Explicit On +Option Strict On +Option Infer On + +Namespace Global.{namespaceName} + + Partial Public Class {classSymbol.Name} + Implements {notifySymbol.ToDisplayString()} + +") + + ' if the class doesn't implement INotifyPropertyChanged already, add it + If Not classSymbol.Interfaces.Contains(CType(notifySymbol, INamedTypeSymbol)) Then + source.Append(" Public Event PropertyChanged As System.ComponentModel.PropertyChangedEventHandler Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged +") + End If + + ' create properties for each field + For Each fieldSymbol In fields + ProcessField(source, fieldSymbol, attributeSymbol) + Next + + source.Append(" + End Class + +End Namespace") + + Return source.ToString() + + End Function + + Private Sub ProcessField(source As StringBuilder, fieldSymbol As IFieldSymbol, attributeSymbol As ISymbol) + + Dim chooseName As Func(Of String, TypedConstant, String) = + Function(fieldName1 As String, overridenNameOpt1 As TypedConstant) As String + + If Not overridenNameOpt1.IsNull Then + Return overridenNameOpt1.Value.ToString() + End If + + fieldName1 = fieldName1.TrimStart("_"c) + If fieldName1.Length = 0 Then + Return String.Empty + End If + + If fieldName1.Length = 1 Then + Return fieldName1.ToUpper() + End If + + Return fieldName1.Substring(0, 1).ToUpper() & fieldName1.Substring(1) + + End Function + + ' get the name and type of the field + Dim fieldName = fieldSymbol.Name + Dim fieldType = fieldSymbol.Type + + ' get the AutoNotify attribute from the field, and any associated data + Dim attributeData = fieldSymbol.GetAttributes().[Single](Function(ad) ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.[Default])) + Dim overridenNameOpt = attributeData.NamedArguments.SingleOrDefault(Function(kvp) kvp.Key = "PropertyName").Value + + Dim propertyName = chooseName(fieldName, overridenNameOpt) + If propertyName.Length = 0 OrElse propertyName = fieldName Then + 'TODO: issue a diagnostic that we can't process this field + Return + End If + + source.Append($" + Public Property {propertyName} As {fieldType} + Get + Return Me.{fieldName} + End Get + Set(value As {fieldType}) + Me.{fieldName} = value + RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(NameOf({propertyName}))) + End Set + End Property +") + + End Sub + + ''' + ''' Created on demand before each generation pass + ''' + Class SyntaxReceiver + Implements ISyntaxReceiver + + Public ReadOnly Property CandidateFields As List(Of FieldDeclarationSyntax) = New List(Of FieldDeclarationSyntax) + + ''' + ''' Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation + ''' + Public Sub OnVisitSyntaxNode(syntaxNode As SyntaxNode) Implements ISyntaxReceiver.OnVisitSyntaxNode + ' any field with at least one attribute is a candidate for property generation + If TypeOf syntaxNode Is FieldDeclarationSyntax Then + Dim fieldDeclarationSyntax = TryCast(syntaxNode, FieldDeclarationSyntax) + If fieldDeclarationSyntax.AttributeLists.Count > 0 Then + CandidateFields.Add(fieldDeclarationSyntax) + End If + End If + End Sub + + End Class + + End Class + +End Namespace diff --git a/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/CsvGenerator.props b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/CsvGenerator.props new file mode 100644 index 000000000..0741032bb --- /dev/null +++ b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/CsvGenerator.props @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/CsvGenerator.vb b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/CsvGenerator.vb new file mode 100644 index 000000000..fa7d13b90 --- /dev/null +++ b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/CsvGenerator.vb @@ -0,0 +1,208 @@ +Option Explicit On +Option Infer On +Option Strict On + +Imports System.IO +Imports System.Text + +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Text + +Imports NotVisualBasic.FileIO + +' CsvTextFileParser from https://github.com/22222/CsvTextFieldParser adding suppression rules for default VS config + +Namespace SourceGeneratorSamples + + + Public Class CsvGenerator + Implements ISourceGenerator + + Public Enum CsvLoadType + Startup + OnDemand + End Enum + + Public Sub Initialize(context As GeneratorInitializationContext) Implements Microsoft.CodeAnalysis.ISourceGenerator.Initialize + + End Sub + + Public Sub Execute(context As GeneratorExecutionContext) Implements ISourceGenerator.Execute + Dim options As IEnumerable(Of (CsvLoadType, Boolean, AdditionalText)) = GetLoadOptions(context) + Dim nameCodeSequence As IEnumerable(Of (Name As String, Code As String)) = SourceFilesFromAdditionalFiles(options) + For Each entry In nameCodeSequence + context.AddSource($"Csv_{entry.Name}", SourceText.From(entry.Code, Encoding.UTF8)) + Next + End Sub + + ' Guesses type of property for the object from the value of a csv field + Public Shared Function GetCsvFieldType(exemplar As String) As String + Dim garbageBoolean As Boolean + Dim garbageInteger As Integer + Dim garbageDouble As Double + Select Case True + Case Boolean.TryParse(exemplar, garbageBoolean) : Return "Boolean" + Case Integer.TryParse(exemplar, garbageInteger) : Return "Integer" + Case Double.TryParse(exemplar, garbageDouble) : Return "Double" + Case Else : Return "String" + End Select + End Function + + ' Examines the header row and the first row in the csv file to gather all header types and names + ' Also it returns the first row of data, because it must be read to figure out the types, + ' As the CsvTextFieldParser cannot 'Peek' ahead of one line. If there is no first line, + ' it consider all properties as strings. The generator returns an empty list of properly + ' typed objects in such case. If the file is completely empty, an error is generated. + Public Shared Function ExtractProperties(parser As CsvTextFieldParser) As (Types As String(), Names As String(), Fields As String()) + + Dim headerFields = parser.ReadFields() + If headerFields Is Nothing Then + Throw New Exception("Empty csv file!") + End If + + Dim firstLineFields = parser.ReadFields() + If firstLineFields Is Nothing Then + Return (Enumerable.Repeat("String", headerFields.Length).ToArray(), headerFields, firstLineFields) + Else + Return (firstLineFields.[Select](Function(field) GetCsvFieldType(field)).ToArray(), headerFields.[Select](New Func(Of String, String)(AddressOf StringToValidPropertyName)).ToArray(), firstLineFields) + End If + + End Function + + ' Adds a class to the `CSV` namespace for each `csv` file passed in. The class has a static property + ' named `All` that returns the list of strongly typed objects generated on demand at first access. + ' There is the slight chance of a race condition in a multi-thread program, but the result is relatively benign + ' , loading the collection multiple times instead of once. Measures could be taken to avoid that. + Public Shared Function GenerateClassFile(className As String, csvText As String, loadTime As CsvLoadType, cacheObjects As Boolean) As String + + Dim sb As New StringBuilder + Dim parser As New CsvTextFieldParser(New StringReader(csvText)) + + ''' Imports + sb.Append("Option Explicit On +Option Strict On +Option Infer On + +Imports System.Collections.Generic + +Namespace Global.CSV +") + + ''' Class Definition + sb.Append($" + Public Class {className} + +") + + If loadTime = CsvLoadType.Startup Then + sb.Append($" Shared Sub New() + Dim x = All + End Sub + +") + End If + + Dim tupleTemp = ExtractProperties(parser) : Dim types = tupleTemp.Types, names = tupleTemp.Names, fields = tupleTemp.Fields + Dim minLen = Math.Min(types.Length, names.Length) + + For i = 0 To minLen - 1 + sb.AppendLine($" Public Property {StringToValidPropertyName(names(i))} As {types(i)}") + Next + + ''' Loading data + sb.Append($" + Private Shared m_all As IEnumerable(Of {className}) + + Public Shared ReadOnly Property All As IEnumerable(Of {className}) + Get +") + + If cacheObjects Then + sb.Append(" If m_all IsNot Nothing Then + Return m_all + End If +") + End If + + sb.Append($" Dim l As New List(Of {className})() + Dim c As {className} +") + + Do + + If fields Is Nothing Then + Continue Do + End If + If fields.Length < minLen Then + Throw New Exception("Not enough fields in CSV file.") + End If + + sb.AppendLine($" c = New {className}()") + + Dim value As String '= "" + For i As Integer = 0 To minLen - 1 + ' Wrap strings in quotes. + value = If(GetCsvFieldType(fields(i)) = "String", $"""{fields(i).Trim().Trim(New Char() {""""c})}""", fields(i)) + sb.AppendLine($" c.{names(i)} = {value}") + Next + + sb.AppendLine(" l.Add(c)") + + fields = parser.ReadFields() + + Loop While fields IsNot Nothing + + sb.Append($" m_all = l + Return l +") + + ' Close things (property, class, namespace) + sb.Append(" End Get + End Property + + End Class + +End Namespace") + + Return sb.ToString() + + End Function + + Private Shared Function StringToValidPropertyName(s As String) As String + s = s.Trim() + s = If(Char.IsLetter(s(0)), Char.ToUpper(s(0)) & s.Substring(1), s) + s = If(Char.IsDigit(s.Trim()(0)), "_" & s, s) + s = New String(s.[Select](Function(ch) If(Char.IsDigit(ch) OrElse Char.IsLetter(ch), ch, "_"c)).ToArray()) + Return s + End Function + + Private Shared Function SourceFilesFromAdditionalFile(loadType As CsvLoadType, cacheObjects As Boolean, file As AdditionalText) As IEnumerable(Of (Name As String, Code As String)) + Dim className = Path.GetFileNameWithoutExtension(file.Path) + Dim csvText = file.GetText().ToString() + Return New(String, String)() {(className, GenerateClassFile(className, csvText, loadType, cacheObjects))} + End Function + + Private Shared Function SourceFilesFromAdditionalFiles(pathsData As IEnumerable(Of (LoadType As CsvLoadType, CacheObjects As Boolean, File As AdditionalText))) As IEnumerable(Of (Name As String, Code As String)) + Return pathsData.SelectMany(Function(d) SourceFilesFromAdditionalFile(d.LoadType, d.CacheObjects, d.File)) + End Function + + Private Shared Iterator Function GetLoadOptions(context As GeneratorExecutionContext) As IEnumerable(Of (LoadType As CsvLoadType, CacheObjects As Boolean, File As AdditionalText)) + For Each file In context.AdditionalFiles + If Path.GetExtension(file.Path).Equals(".csv", StringComparison.OrdinalIgnoreCase) Then + ' are there any options for it? + Dim loadTimeString As String = Nothing + context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.CsvLoadType", loadTimeString) + Dim loadType As CsvLoadType = Nothing + [Enum].TryParse(loadTimeString, ignoreCase:=True, loadType) + Dim cacheObjectsString As String = Nothing + context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.CacheObjects", cacheObjectsString) + Dim cacheObjects As Boolean = Nothing + Boolean.TryParse(cacheObjectsString, cacheObjects) + Yield (loadType, cacheObjects, file) + End If + Next + End Function + + End Class + +End Namespace diff --git a/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.vb b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.vb new file mode 100644 index 000000000..ff4c51ba2 --- /dev/null +++ b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.vb @@ -0,0 +1,66 @@ +Option Explicit On +Option Infer On +Option Strict On + +Imports System.Text + +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Text + +Namespace SourceGeneratorSamples + + + Public Class HelloWorldGenerator + Implements ISourceGenerator + + Public Sub Initialize(context As GeneratorInitializationContext) Implements ISourceGenerator.Initialize + ' No initialization required + End Sub + + Public Sub Execute(context As GeneratorExecutionContext) Implements ISourceGenerator.Execute + + ' begin creating the source we'll inject into the users compilation + + Dim sourceBuilder = New StringBuilder("Option Explicit On +Option Strict On +Option Infer On + +Namespace Global.HelloWorldGenerated + + Public Module HelloWorld + + Public Sub SayHello() + + Console.WriteLine(""Hello from generated code!"") + Console.WriteLine(""The following syntax trees existed in the compilation that created this program:"") +") + + ' for testing... let's include a comment with the current date/time. + sourceBuilder.AppendLine($" ' Generated at {DateTime.Now}") + + ' using the context, get a list of syntax trees in the users compilation + ' add the filepath of each tree to the class we're building + + For Each tree In context.Compilation.SyntaxTrees + sourceBuilder.AppendLine($" Console.WriteLine("" - {tree.FilePath}"")") + Next + + ' finish creating the source to inject + + sourceBuilder.Append(" + + End Sub + + End Module + +End Namespace") + + ' inject the created source into the users compilation + + context.AddSource("HelloWorldGenerated", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)) + + End Sub + + End Class + +End Namespace diff --git a/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.vb b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.vb new file mode 100644 index 000000000..d1ecd628f --- /dev/null +++ b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.vb @@ -0,0 +1,102 @@ +Option Explicit On +Option Infer On +Option Strict On + +Imports System.IO +Imports System.Text +Imports System.Xml + +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Text + +Namespace SourceGeneratorSamples + + + Public Class SettingsXmlGenerator + Implements ISourceGenerator + + Public Sub Initialize(context As GeneratorInitializationContext) Implements ISourceGenerator.Initialize + + End Sub + + Public Sub Execute(context As GeneratorExecutionContext) Implements ISourceGenerator.Execute + ' Using the context, get any additional files that end in .xmlsettings + For Each settingsFile In context.AdditionalFiles.Where(Function(at) at.Path.EndsWith(".xmlsettings")) + ProcessSettingsFile(settingsFile, context) + Next + End Sub + + Private Sub ProcessSettingsFile(xmlFile As AdditionalText, context As GeneratorExecutionContext) + + ' try and load the settings file + Dim xmlDoc As New XmlDocument + Dim text = xmlFile.GetText(context.CancellationToken).ToString() + Try + xmlDoc.LoadXml(text) + Catch + 'TODO: issue a diagnostic that says we couldn't parse it + Return + End Try + + ' create a class in the XmlSetting class that represnts this entry, and a static field that contains a singleton instance. + Dim fileName = Path.GetFileName(xmlFile.Path) + Dim name = xmlDoc.DocumentElement.GetAttribute("name") + + Dim sb = New StringBuilder($"Option Explicit On +Option Strict On +Option Infer On + +Imports System.Xml + +Namespace Global.AutoSettings + + Partial Public Class XmlSettings + + Public Shared ReadOnly Property {name} As {name}Settings = New {name}Settings(""{fileName}"") + + Public Class {name}Settings + + Private m_xmlDoc As New XmlDocument() + + Private m_fileName As String + + Friend Sub New(fileName As String) + m_fileName = fileName + m_xmlDoc.Load(m_fileName) + End Sub + + Public Function GetLocation() As String + Return m_fileName + End Function") + + For i = 0 To xmlDoc.DocumentElement.ChildNodes.Count - 1 + + Dim setting = CType(xmlDoc.DocumentElement.ChildNodes(i), XmlElement) + Dim settingName = setting.GetAttribute("name") + Dim settingType = setting.GetAttribute("type") + + sb.Append($" + + Public ReadOnly Property {settingName} As {settingType} + Get + Return DirectCast(Convert.ChangeType(DirectCast(m_xmlDoc.DocumentElement.ChildNodes({i}), XmlElement).InnerText, GetType({settingType})), {settingType}) + End Get + End Property") + + Next + + sb.Append(" + + End Class + + End Class + +End Namespace") + + context.AddSource($"Settings_{name}", SourceText.From(sb.ToString(), Encoding.UTF8)) + + End Sub + + End Class + +End Namespace diff --git a/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/VisualBasicSourceGeneratorSamples.vbproj b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/VisualBasicSourceGeneratorSamples.vbproj new file mode 100644 index 000000000..9e9f12565 --- /dev/null +++ b/samples/VisualBasic/SourceGenerators/SourceGeneratorSamples/VisualBasicSourceGeneratorSamples.vbproj @@ -0,0 +1,27 @@ + + + + netstandard2.0 + + + + + + + + + + + + + + $(GetTargetPathDependsOn);GetDependencyTargetPaths + + + + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/SourceGenerators/SourceGenerators.sln b/samples/VisualBasic/SourceGenerators/SourceGenerators.sln new file mode 100644 index 000000000..6b7947985 --- /dev/null +++ b/samples/VisualBasic/SourceGenerators/SourceGenerators.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30022.13 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "GeneratedDemo", "GeneratedDemo\GeneratedDemo.vbproj", "{08612C19-D039-44D1-9030-D192CEAF05BB}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "SourceGeneratorSamples", "SourceGeneratorSamples\SourceGeneratorSamples.vbproj", "{90BDB1C3-E353-448C-8A29-E5B2EF10670B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {08612C19-D039-44D1-9030-D192CEAF05BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08612C19-D039-44D1-9030-D192CEAF05BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08612C19-D039-44D1-9030-D192CEAF05BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08612C19-D039-44D1-9030-D192CEAF05BB}.Release|Any CPU.Build.0 = Release|Any CPU + {90BDB1C3-E353-448C-8A29-E5B2EF10670B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90BDB1C3-E353-448C-8A29-E5B2EF10670B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90BDB1C3-E353-448C-8A29-E5B2EF10670B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90BDB1C3-E353-448C-8A29-E5B2EF10670B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {623C84B6-B8A4-4F29-8E68-4ED37D4529D5} + EndGlobalSection +EndGlobal diff --git a/samples/VisualBasic/TreeTransforms/TransformVisitor.vb b/samples/VisualBasic/TreeTransforms/TransformVisitor.vb new file mode 100644 index 000000000..1520db37d --- /dev/null +++ b/samples/VisualBasic/TreeTransforms/TransformVisitor.vb @@ -0,0 +1,325 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Public Class TransformVisitor + Inherits VisualBasicSyntaxRewriter + + Private ReadOnly tree As SyntaxTree + Private ReadOnly transformKind As TransformKind + + Public Sub New(tree As SyntaxTree, transformKind As TransformKind) + Me.tree = tree + Me.transformKind = transformKind + End Sub + + Public Overrides Function VisitLiteralExpression(node As LiteralExpressionSyntax) As SyntaxNode + node = DirectCast(MyBase.VisitLiteralExpression(node), LiteralExpressionSyntax) + Dim token = node.Token + + If (transformKind = TransformKind.TrueToFalse) AndAlso (node.Kind = SyntaxKind.TrueLiteralExpression) Then + Dim newToken = SyntaxFactory.Token(token.LeadingTrivia, SyntaxKind.FalseKeyword, token.TrailingTrivia) + Return SyntaxFactory.LiteralExpression(SyntaxKind.FalseLiteralExpression, newToken) + End If + + If (transformKind = TransformKind.FalseToTrue) AndAlso (node.Kind = SyntaxKind.FalseLiteralExpression) Then + Dim newToken = SyntaxFactory.Token(token.LeadingTrivia, SyntaxKind.TrueKeyword, token.TrailingTrivia) + Return SyntaxFactory.LiteralExpression(SyntaxKind.TrueLiteralExpression, newToken) + End If + + Return node + End Function + + Public Overrides Function VisitPredefinedType(node As PredefinedTypeSyntax) As SyntaxNode + node = DirectCast(MyBase.VisitPredefinedType(node), PredefinedTypeSyntax) + Dim token = node.Keyword + + If (transformKind = TransformKind.IntTypeToLongType) AndAlso (token.Kind = SyntaxKind.IntegerKeyword) Then + Dim longToken = SyntaxFactory.Token(token.LeadingTrivia, SyntaxKind.LongKeyword, token.TrailingTrivia) + Return SyntaxFactory.PredefinedType(longToken) + End If + + Return node + End Function + + Public Overrides Function VisitModuleBlock(ByVal node As ModuleBlockSyntax) As SyntaxNode + Return MyBase.VisitModuleBlock(node) + End Function + + Public Overrides Function VisitClassBlock(ByVal node As ClassBlockSyntax) As SyntaxNode + Dim classStatement = node.ClassStatement + Dim classKeyword = classStatement.ClassKeyword + Dim endStatement = node.EndClassStatement + Dim endBlockKeyword = endStatement.BlockKeyword + + If transformKind = TransformKind.ClassToStructure Then + Dim structureKeyword = SyntaxFactory.Token(classKeyword.LeadingTrivia, SyntaxKind.StructureKeyword, classKeyword.TrailingTrivia) + Dim endStructureKeyword = SyntaxFactory.Token(endBlockKeyword.LeadingTrivia, SyntaxKind.StructureKeyword, endBlockKeyword.TrailingTrivia) + Dim newStructureStatement = SyntaxFactory.StructureStatement(classStatement.AttributeLists, classStatement.Modifiers, structureKeyword, classStatement.Identifier, classStatement.TypeParameterList) + Dim newEndStatement = SyntaxFactory.EndStructureStatement(endStatement.EndKeyword, endStructureKeyword) + Return SyntaxFactory.StructureBlock(newStructureStatement, node.Inherits, node.Implements, node.Members, newEndStatement) + End If + + Return node + End Function + + Public Overrides Function VisitStructureBlock(ByVal node As StructureBlockSyntax) As SyntaxNode + Dim structureStatement = node.StructureStatement + Dim structureKeyword = structureStatement.StructureKeyword + Dim endStatement = node.EndStructureStatement + Dim endBlockKeyword = endStatement.BlockKeyword + + If transformKind = TransformKind.StructureToClass Then + Dim classKeyword = SyntaxFactory.Token(structureKeyword.LeadingTrivia, SyntaxKind.ClassKeyword, structureKeyword.TrailingTrivia) + Dim endClassKeyword = SyntaxFactory.Token(endBlockKeyword.LeadingTrivia, SyntaxKind.ClassKeyword, endBlockKeyword.TrailingTrivia) + Dim newClassStatement = SyntaxFactory.ClassStatement(structureStatement.AttributeLists, structureStatement.Modifiers, classKeyword, structureStatement.Identifier, structureStatement.TypeParameterList) + Dim newEndStatement = SyntaxFactory.EndClassStatement(endStatement.EndKeyword, endClassKeyword) + Return SyntaxFactory.ClassBlock(newClassStatement, node.Inherits, node.Implements, node.Members, newEndStatement) + End If + + Return node + End Function + + Public Overrides Function VisitInterfaceBlock(ByVal node As InterfaceBlockSyntax) As SyntaxNode + Return MyBase.VisitInterfaceBlock(node) + End Function + + Public Overrides Function VisitOrdering(node As OrderingSyntax) As SyntaxNode + node = DirectCast(MyBase.VisitOrdering(node), OrderingSyntax) + Dim orderingKind = node.AscendingOrDescendingKeyword + + If (transformKind = TransformKind.OrderByAscToOrderByDesc) AndAlso (orderingKind.Kind = SyntaxKind.AscendingKeyword) Then + Dim descToken = SyntaxFactory.Token(orderingKind.LeadingTrivia, SyntaxKind.DescendingKeyword, orderingKind.TrailingTrivia) + Return SyntaxFactory.Ordering(SyntaxKind.DescendingOrdering, node.Expression, descToken) + End If + + If (transformKind = TransformKind.OrderByDescToOrderByAsc) AndAlso (orderingKind.Kind = SyntaxKind.DescendingKeyword) Then + Dim ascToken = SyntaxFactory.Token(orderingKind.LeadingTrivia, SyntaxKind.AscendingKeyword, orderingKind.TrailingTrivia) + Return SyntaxFactory.Ordering(SyntaxKind.AscendingOrdering, node.Expression, ascToken) + End If + + Return node + End Function + + Public Overrides Function VisitAssignmentStatement(node As AssignmentStatementSyntax) As SyntaxNode + node = DirectCast(MyBase.VisitAssignmentStatement(node), AssignmentStatementSyntax) + Dim left = node.Left + Dim right = node.Right + Dim operatorToken = node.OperatorToken + + If (transformKind = TransformKind.AddAssignmentToAssignment) AndAlso (node.Kind = SyntaxKind.AddAssignmentStatement) Then + Dim equalsToken = SyntaxFactory.Token(operatorToken.LeadingTrivia, SyntaxKind.EqualsToken, operatorToken.TrailingTrivia) + Dim newLeft = left.WithLeadingTrivia(SyntaxTriviaList.Empty) + Dim plusToken = SyntaxFactory.Token(operatorToken.LeadingTrivia, SyntaxKind.PlusToken, operatorToken.TrailingTrivia) + Dim addExpression = SyntaxFactory.BinaryExpression(SyntaxKind.AddExpression, newLeft, plusToken, right) + + Return SyntaxFactory.SimpleAssignmentStatement(left, equalsToken, addExpression) + End If + + Return node + End Function + + Public Overrides Function VisitDirectCastExpression(node As DirectCastExpressionSyntax) As SyntaxNode + node = DirectCast(MyBase.VisitDirectCastExpression(node), DirectCastExpressionSyntax) + Dim keyword = node.Keyword + + If (transformKind = TransformKind.DirectCastToTryCast) AndAlso (node.Kind = SyntaxKind.DirectCastExpression) Then + Dim tryCastKeyword = SyntaxFactory.Token(keyword.LeadingTrivia, SyntaxKind.TryCastKeyword, keyword.TrailingTrivia) + + Return SyntaxFactory.TryCastExpression(tryCastKeyword, node.OpenParenToken, node.Expression, node.CommaToken, node.Type, node.CloseParenToken) + End If + + Return node + End Function + + Public Overrides Function VisitTryCastExpression(node As TryCastExpressionSyntax) As SyntaxNode + node = DirectCast(MyBase.VisitTryCastExpression(node), TryCastExpressionSyntax) + Dim keyword = node.Keyword + + If (transformKind = TransformKind.TryCastToDirectCast) AndAlso (node.Kind = SyntaxKind.TryCastExpression) Then + Dim directCastKeyword = SyntaxFactory.Token(keyword.LeadingTrivia, SyntaxKind.DirectCastKeyword, keyword.TrailingTrivia) + + Return SyntaxFactory.DirectCastExpression(directCastKeyword, node.OpenParenToken, node.Expression, node.CommaToken, node.Type, node.CloseParenToken) + End If + + Return node + End Function + + Public Overrides Function VisitVariableDeclarator(node As VariableDeclaratorSyntax) As SyntaxNode + node = DirectCast(MyBase.VisitVariableDeclarator(node), VariableDeclaratorSyntax) + Dim names = node.Names + Dim asClause = node.AsClause + Dim initializer = node.Initializer + + If (transformKind = TransformKind.InitVariablesToNothing) AndAlso (initializer Is Nothing) AndAlso (names.Count = 1) Then + Dim newEqualsToken = SyntaxFactory.Token(SyntaxFactory.TriviaList(SyntaxFactory.WhitespaceTrivia(" ")), SyntaxKind.EqualsToken, SyntaxFactory.TriviaList(SyntaxFactory.WhitespaceTrivia(" "))) + Dim newNothingToken = SyntaxFactory.Token(SyntaxKind.NothingKeyword) + Dim newNothingExpression = SyntaxFactory.NothingLiteralExpression(newNothingToken) + Dim newInitializer = SyntaxFactory.EqualsValue(newEqualsToken, newNothingExpression) + + Return node.Update(node.Names, node.AsClause, newInitializer) + End If + + Return node + End Function + + Public Overrides Function VisitParameter(node As ParameterSyntax) As SyntaxNode + node = DirectCast(MyBase.VisitParameter(node), ParameterSyntax) + + If (transformKind = TransformKind.ByRefParamToByValParam) OrElse (transformKind = TransformKind.ByValParamToByRefParam) Then + Dim listOfModifiers = New List(Of SyntaxToken) + + For Each modifier In node.Modifiers + Dim modifierToken = modifier + + If (modifier.Kind = SyntaxKind.ByValKeyword) AndAlso (transformKind = TransformKind.ByValParamToByRefParam) Then + modifierToken = SyntaxFactory.Token(modifierToken.LeadingTrivia, SyntaxKind.ByRefKeyword, modifierToken.TrailingTrivia) + ElseIf (modifier.Kind = SyntaxKind.ByRefKeyword) AndAlso (transformKind = TransformKind.ByRefParamToByValParam) Then + modifierToken = SyntaxFactory.Token(modifierToken.LeadingTrivia, SyntaxKind.ByValKeyword, modifierToken.TrailingTrivia) + End If + + listOfModifiers.Add(modifierToken) + Next + + Dim newModifiers = SyntaxFactory.TokenList(listOfModifiers) + Return SyntaxFactory.Parameter(node.AttributeLists, newModifiers, node.Identifier, node.AsClause, node.Default) + End If + + Return node + End Function + + Public Overrides Function VisitDoLoopBlock(node As DoLoopBlockSyntax) As SyntaxNode + node = DirectCast(MyBase.VisitDoLoopBlock(node), DoLoopBlockSyntax) + Dim beginLoop = node.DoStatement + Dim endLoop = node.LoopStatement + + If (transformKind = TransformKind.DoBottomTestToDoTopTest) AndAlso (node.Kind = SyntaxKind.DoLoopWhileBlock OrElse node.Kind = SyntaxKind.DoLoopUntilBlock) Then + Dim newDoKeyword = SyntaxFactory.Token(beginLoop.DoKeyword.LeadingTrivia, SyntaxKind.DoKeyword, endLoop.LoopKeyword.TrailingTrivia) + Dim newLoopKeyword = SyntaxFactory.Token(endLoop.LoopKeyword.LeadingTrivia, endLoop.LoopKeyword.Kind, beginLoop.DoKeyword.TrailingTrivia) + Dim newBegin = SyntaxFactory.DoStatement(If(endLoop.Kind = SyntaxKind.LoopWhileStatement, SyntaxKind.DoWhileStatement, SyntaxKind.DoUntilStatement), newDoKeyword, endLoop.WhileOrUntilClause) + Dim newEnd = SyntaxFactory.SimpleLoopStatement().WithLoopKeyword(newLoopKeyword) + Return SyntaxFactory.DoLoopBlock(If(endLoop.Kind = SyntaxKind.LoopWhileStatement, SyntaxKind.DoWhileLoopBlock, SyntaxKind.DoUntilLoopBlock), newBegin, node.Statements, newEnd) + End If + + If (transformKind = TransformKind.DoTopTestToDoBottomTest) AndAlso (node.Kind = SyntaxKind.DoWhileLoopBlock OrElse node.Kind = SyntaxKind.DoUntilLoopBlock) Then + Dim newDoKeyword = SyntaxFactory.Token(beginLoop.DoKeyword.LeadingTrivia, SyntaxKind.DoKeyword, endLoop.LoopKeyword.TrailingTrivia) + Dim newLoopKeyword = SyntaxFactory.Token(endLoop.LoopKeyword.LeadingTrivia, endLoop.LoopKeyword.Kind, beginLoop.DoKeyword.TrailingTrivia) + Dim newBegin = SyntaxFactory.SimpleDoStatement().WithDoKeyword(newDoKeyword) + Dim newEnd = SyntaxFactory.LoopStatement(If(beginLoop.Kind = SyntaxKind.DoWhileStatement, SyntaxKind.LoopWhileStatement, SyntaxKind.LoopUntilStatement), newLoopKeyword, beginLoop.WhileOrUntilClause) + Return SyntaxFactory.DoLoopBlock(If(beginLoop.Kind = SyntaxKind.DoWhileStatement, SyntaxKind.DoLoopWhileBlock, SyntaxKind.DoLoopUntilBlock), newBegin, node.Statements, newEnd) + End If + + If (transformKind = TransformKind.DoWhileTopTestToWhile) AndAlso (node.Kind = SyntaxKind.DoWhileLoopBlock OrElse node.Kind = SyntaxKind.DoUntilLoopBlock) Then + Dim endKeyword = SyntaxFactory.Token(endLoop.LoopKeyword.LeadingTrivia, SyntaxKind.EndKeyword) + Dim endWhileKeyword = SyntaxFactory.Token(SyntaxFactory.TriviaList(SyntaxFactory.Whitespace(" ")), SyntaxKind.WhileKeyword, endLoop.LoopKeyword.TrailingTrivia) + Dim endWhile = SyntaxFactory.EndWhileStatement(endKeyword, endWhileKeyword) + Dim beginWhileKeyword = SyntaxFactory.Token(beginLoop.DoKeyword.LeadingTrivia, SyntaxKind.WhileKeyword, beginLoop.DoKeyword.TrailingTrivia) + Dim whileStatement = SyntaxFactory.WhileStatement(beginWhileKeyword, beginLoop.WhileOrUntilClause.Condition) + Return SyntaxFactory.WhileBlock(whileStatement, node.Statements, endWhile) + End If + + Return node + End Function + + Public Overrides Function VisitWhileBlock(node As WhileBlockSyntax) As SyntaxNode + node = DirectCast(MyBase.VisitWhileBlock(node), WhileBlockSyntax) + Dim beginWhile = node.WhileStatement + Dim endWhile = node.EndWhileStatement + + If transformKind = TransformKind.WhileToDoWhileTopTest Then + Dim doKeyword = SyntaxFactory.Token(beginWhile.WhileKeyword.LeadingTrivia, SyntaxKind.DoKeyword, beginWhile.WhileKeyword.TrailingTrivia) + Dim whileKeyword = SyntaxFactory.Token(SyntaxKind.WhileKeyword, SyntaxFactory.TriviaList(SyntaxFactory.Whitespace(" "))) + Dim whileClause = SyntaxFactory.WhileOrUntilClause(SyntaxKind.WhileClause, whileKeyword, beginWhile.Condition) + Dim loopKeyword = SyntaxFactory.Token(endWhile.GetLeadingTrivia(), SyntaxKind.LoopKeyword, endWhile.GetTrailingTrivia()) + Dim endLoop = SyntaxFactory.SimpleLoopStatement().WithLoopKeyword(loopKeyword) + Dim doStatement = SyntaxFactory.DoWhileStatement(doKeyword, whileClause) + + Return SyntaxFactory.DoWhileLoopBlock(doStatement, node.Statements, endLoop) + End If + + Return node + End Function + + Public Overrides Function VisitExitStatement(node As ExitStatementSyntax) As SyntaxNode + node = DirectCast(MyBase.VisitExitStatement(node), ExitStatementSyntax) + Dim blockKeyword = node.BlockKeyword + Dim exitKeyword = node.ExitKeyword + + If (transformKind = TransformKind.WhileToDoWhileTopTest) AndAlso (node.Kind = SyntaxKind.ExitWhileStatement) Then + Dim doKeyword = SyntaxFactory.Token(blockKeyword.LeadingTrivia, SyntaxKind.DoKeyword, blockKeyword.TrailingTrivia) + Return SyntaxFactory.ExitDoStatement(exitKeyword, doKeyword) + End If + + If (transformKind = TransformKind.DoWhileTopTestToWhile) AndAlso (node.Kind = SyntaxKind.ExitDoStatement) Then + Dim parent = node.Parent + + 'Update Exit Do to Exit While only for Do-While and NOT for Do-Until + While (parent IsNot Nothing) AndAlso (TryCast(parent, DoLoopBlockSyntax) Is Nothing) + parent = parent.Parent + End While + + Dim doBlock = TryCast(parent, DoLoopBlockSyntax) + + If doBlock IsNot Nothing Then + If (doBlock.DoStatement.WhileOrUntilClause IsNot Nothing) AndAlso (doBlock.DoStatement.WhileOrUntilClause.Kind = SyntaxKind.WhileClause) Then + Dim whileKeyword = SyntaxFactory.Token(blockKeyword.LeadingTrivia, SyntaxKind.WhileKeyword, blockKeyword.TrailingTrivia) + Return SyntaxFactory.ExitWhileStatement(exitKeyword, whileKeyword) + End If + End If + End If + + Return node + End Function + + Public Overrides Function VisitSingleLineIfStatement(node As SingleLineIfStatementSyntax) As SyntaxNode + node = DirectCast(MyBase.VisitSingleLineIfStatement(node), SingleLineIfStatementSyntax) + Dim elseClause = node.ElseClause + + If transformKind = TransformKind.SingleLineIfToMultiLineIf Then + Dim triviaList = node.GetLeadingTrivia() + Dim lastTrivia = If(triviaList.Count = 0, Nothing, triviaList(triviaList.Count - 1)) + Dim leadingTriviaList = SyntaxFactory.TriviaList(lastTrivia, SyntaxFactory.WhitespaceTrivia(" ")) + Dim newIfStatement = SyntaxFactory.IfStatement(node.IfKeyword, node.Condition, node.ThenKeyword) + Dim newIfStatements = GetSequentialListOfStatements(node.Statements, leadingTriviaList) + + Dim newElseBlock As ElseBlockSyntax = Nothing + If elseClause IsNot Nothing Then + Dim newElseKeyword = SyntaxFactory.Token(node.GetLeadingTrivia(), SyntaxKind.ElseKeyword) + Dim newElseStmt = SyntaxFactory.ElseStatement(newElseKeyword) + Dim newStatementsElsePart = GetSequentialListOfStatements(elseClause.Statements, leadingTriviaList) + newElseBlock = SyntaxFactory.ElseBlock(newElseStmt, newStatementsElsePart) + End If + + Dim whiteSpaceTrivia = SyntaxFactory.WhitespaceTrivia(" ") + Dim endKeyword = SyntaxFactory.Token(node.GetLeadingTrivia(), SyntaxKind.EndKeyword) + Dim blockKeyword = SyntaxFactory.Token(SyntaxFactory.TriviaList(whiteSpaceTrivia), SyntaxKind.IfKeyword) + Dim newEndIf = SyntaxFactory.EndIfStatement(endKeyword, blockKeyword) + + Return SyntaxFactory.MultiLineIfBlock(newIfStatement, newIfStatements, Nothing, newElseBlock, newEndIf) + End If + + Return node + End Function + + Private Function GetSequentialListOfStatements(statements As SyntaxList(Of StatementSyntax), stmtLeadingTrivia As SyntaxTriviaList) As SyntaxList(Of StatementSyntax) + Dim newStatementList = New List(Of StatementSyntax) + + For Each statement In statements + Dim oldFirst = statement.GetFirstToken(includeZeroWidth:=True) + Dim newFirst = oldFirst.WithLeadingTrivia(stmtLeadingTrivia) + newStatementList.Add(statement.ReplaceToken(oldFirst, newFirst)) + Next + + Dim listOfSeparators = New List(Of SyntaxToken) + + For i = 0 To statements.Count - 1 + listOfSeparators.Add(SyntaxFactory.Token(SyntaxKind.StatementTerminatorToken)) + Next + + Return SyntaxFactory.List(newStatementList) + End Function + +End Class diff --git a/samples/VisualBasic/TreeTransforms/Transforms.vb b/samples/VisualBasic/TreeTransforms/Transforms.vb new file mode 100644 index 000000000..a2f560ba7 --- /dev/null +++ b/samples/VisualBasic/TreeTransforms/Transforms.vb @@ -0,0 +1,49 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +''' +''' Kinds of Syntax transforms. +''' +''' +Public Enum TransformKind + IntTypeToLongType + TrueToFalse + FalseToTrue + ClassToStructure + StructureToClass + OrderByAscToOrderByDesc + OrderByDescToOrderByAsc + AddAssignmentToAssignment + DirectCastToTryCast + TryCastToDirectCast + InitVariablesToNothing + ByValParamToByRefParam + ByRefParamToByValParam + DoTopTestToDoBottomTest + DoBottomTestToDoTopTest + WhileToDoWhileTopTest + DoWhileTopTestToWhile + SingleLineIfToMultiLineIf +End Enum + +Public Class Transforms + ''' + ''' Performs a syntax transform of the source code which is passed in as a string. The transform to be performed is also passed as an argument + ''' + ''' Text of the source code which is to be transformed + ''' The kind of Syntax Transform that needs to be performed on the source + ''' Transformed source code as a string + ''' + Public Shared Function Transform(sourceText As String, transformKind As TransformKind) As String + Dim sourceTree = SyntaxFactory.ParseSyntaxTree(sourceText) + Dim visitor As New TransformVisitor(sourceTree, transformKind) + + Return visitor.Visit(sourceTree.GetRoot()).ToFullString() + End Function + +End Class diff --git a/samples/VisualBasic/TreeTransforms/TreeTransforms.VisualBasic.UnitTests.vbproj b/samples/VisualBasic/TreeTransforms/TreeTransforms.VisualBasic.UnitTests.vbproj new file mode 100644 index 000000000..2b6a73337 --- /dev/null +++ b/samples/VisualBasic/TreeTransforms/TreeTransforms.VisualBasic.UnitTests.vbproj @@ -0,0 +1,11 @@ + + + + net472 + + + + + + + diff --git a/samples/VisualBasic/TreeTransforms/TreeTransforms.vb b/samples/VisualBasic/TreeTransforms/TreeTransforms.vb new file mode 100644 index 000000000..336489fef --- /dev/null +++ b/samples/VisualBasic/TreeTransforms/TreeTransforms.vb @@ -0,0 +1,611 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + + +Imports Xunit + +Public Class TreeTransformTests + + + Public Sub IntTypeToLongTypeTest() + Dim input As String = + +Module Module1 + Sub Main() + Dim x As Integer = 10 + Dim l1 As List(Of Integer) = New List(Of Integer) + End Sub +End Module +.Value + + Dim expected_transform = + +Module Module1 + Sub Main() + Dim x As Long = 10 + Dim l1 As List(Of Long) = New List(Of Long) + End Sub +End Module +.Value + + Dim actual_transform = Transforms.Transform(input, TransformKind.IntTypeToLongType) + + Assert.Equal(expected_transform, actual_transform) + End Sub + + + Public Sub TrueToFalseTest() + Dim input As String = + +Module Module1 + Sub Main() + Dim b As Boolean = True + If True Then + End If + End Sub +End Module +.Value + + Dim expected_transform = + +Module Module1 + Sub Main() + Dim b As Boolean = False + If False Then + End If + End Sub +End Module +.Value + + Dim actual_transform = Transforms.Transform(input, TransformKind.TrueToFalse) + + Assert.Equal(expected_transform, actual_transform) + End Sub + + + Public Sub FalseToTrueTest() + Dim input As String = + +Module Module1 + Sub Main() + Dim b As Boolean = False + If False Then + End If + End Sub +End Module +.Value + + Dim expected_transform = + +Module Module1 + Sub Main() + Dim b As Boolean = True + If True Then + End If + End Sub +End Module +.Value + + Dim actual_transform = Transforms.Transform(input, TransformKind.FalseToTrue) + + Assert.Equal(expected_transform, actual_transform) + End Sub + + + Public Sub ClassToStructureTest() + Dim input As String = + +Class Test + Sub Main() + End Sub +End Class +.Value + + Dim expected_transform = + +Structure Test + Sub Main() + End Sub +End Structure +.Value + + Dim actual_transform = Transforms.Transform(input, TransformKind.ClassToStructure) + + Assert.Equal(expected_transform, actual_transform) + End Sub + + + Public Sub StructureToClassTest() + Dim input As String = + +Structure Test + Sub Main() + End Sub +End Structure +.Value + + Dim expected_transform = + +Class Test + Sub Main() + End Sub +End Class +.Value + + Dim actual_transform = Transforms.Transform(input, TransformKind.StructureToClass) + + Assert.Equal(expected_transform, actual_transform) + End Sub + + + Public Sub OrderByAscToOrderByDescTest() + Dim input As String = + +Imports System.Linq + +Module Module1 + Sub Main() + Dim numbers() = {3, 1, 4, 6, 10} + + Dim sortedNumbers = From number In numbers + Order By number Ascending + Select number + + For Each number In sortedNumbers + System.Console.WriteLine(number) + Next + + End Sub +End Module +.Value + + Dim expected_transform = + +Imports System.Linq + +Module Module1 + Sub Main() + Dim numbers() = {3, 1, 4, 6, 10} + + Dim sortedNumbers = From number In numbers + Order By number Descending + Select number + + For Each number In sortedNumbers + System.Console.WriteLine(number) + Next + + End Sub +End Module +.Value + + Dim actual_transform = Transforms.Transform(input, TransformKind.OrderByAscToOrderByDesc) + + Assert.Equal(expected_transform, actual_transform) + End Sub + + + Public Sub OrderByDescToOrderByAscTest() + Dim input As String = + +Imports System.Linq + +Module Module1 + Sub Main() + Dim numbers() = {3, 1, 4, 6, 10} + + Dim sortedNumbers = From number In numbers + Order By number Descending + Select number + + For Each number In sortedNumbers + System.Console.WriteLine(number) + Next + + End Sub +End Module +.Value + + Dim expected_transform = + +Imports System.Linq + +Module Module1 + Sub Main() + Dim numbers() = {3, 1, 4, 6, 10} + + Dim sortedNumbers = From number In numbers + Order By number Ascending + Select number + + For Each number In sortedNumbers + System.Console.WriteLine(number) + Next + + End Sub +End Module +.Value + + Dim actual_transform = Transforms.Transform(input, TransformKind.OrderByDescToOrderByAsc) + + Assert.Equal(expected_transform, actual_transform) + End Sub + + + Public Sub AddAssignmentToAssignmentTest() + Dim input As String = + +Module Module1 + Sub Main() + Dim x As Integer = 10 + Dim y As Integer = 20 + + x += y + + End Sub +End Module +.Value + + Dim expected_transform = + +Module Module1 + Sub Main() + Dim x As Integer = 10 + Dim y As Integer = 20 + + x = x + y + + End Sub +End Module +.Value + + Dim actual_transform = Transforms.Transform(input, TransformKind.AddAssignmentToAssignment) + + Assert.Equal(expected_transform, actual_transform) + End Sub + + + Public Sub DirectCastToTryCastTest() + Dim input As String = + +Module Module1 + Sub Main() + Dim x As Integer = 10 + Dim y = DirectCast(x, Object) + End Sub +End Module +.Value + + Dim expected_transform = + +Module Module1 + Sub Main() + Dim x As Integer = 10 + Dim y = TryCast(x, Object) + End Sub +End Module +.Value + + Dim actual_transform = Transforms.Transform(input, TransformKind.DirectCastToTryCast) + + Assert.Equal(expected_transform, actual_transform) + End Sub + + + Public Sub TryCastToDirectCastTest() + Dim input As String = + +Module Module1 + Sub Main() + Dim x As Integer = 10 + Dim y = TryCast(x, Object) + End Sub +End Module +.Value + + Dim expected_transform = + +Module Module1 + Sub Main() + Dim x As Integer = 10 + Dim y = DirectCast(x, Object) + End Sub +End Module +.Value + + Dim actual_transform = Transforms.Transform(input, TransformKind.TryCastToDirectCast) + + Assert.Equal(expected_transform, actual_transform) + End Sub + + + Public Sub InitVariablesToNothingTest() + Dim input As String = + +Module Module1 + Sub Main() + Dim x As Integer, y As Object, d As Decimal, m1 + End Sub +End Module +.Value + + Dim expected_transform = + +Module Module1 + Sub Main() + Dim x As Integer = Nothing, y As Object = Nothing, d As Decimal = Nothing, m1 + = Nothing End Sub +End Module +.Value + + Dim actual_transform = Transforms.Transform(input, TransformKind.InitVariablesToNothing) + + Assert.Equal(expected_transform, actual_transform) + End Sub + + + Public Sub ByValParamToByRefParamTest() + Dim input As String = + +Module Module1 + Sub Method1(ByVal param1 As Integer, ByRef param2 As Single, ByVal param3 As Decimal) + + End Sub +End Module +.Value + + Dim expected_transform = + +Module Module1 + Sub Method1(ByRef param1 As Integer, ByRef param2 As Single, ByRef param3 As Decimal) + + End Sub +End Module +.Value + + Dim actual_transform = Transforms.Transform(input, TransformKind.ByValParamToByRefParam) + + Assert.Equal(expected_transform, actual_transform) + End Sub + + + Public Sub ByRefParamToByValParamTest() + Dim input As String = + +Module Module1 + Sub Method1(ByVal param1 As Integer, ByRef param2 As Single, ByVal param3 As Decimal) + + End Sub +End Module +.Value + + Dim expected_transform = + +Module Module1 + Sub Method1(ByVal param1 As Integer, ByVal param2 As Single, ByVal param3 As Decimal) + + End Sub +End Module +.Value + + Dim actual_transform = Transforms.Transform(input, TransformKind.ByRefParamToByValParam) + + Assert.Equal(expected_transform, actual_transform) + End Sub + + + Public Sub DoTopTestToDoBottomTestTest() + Dim input As String = + +Module Module1 + Sub Main() + Dim index As Integer = 0 + Dim condition As Boolean = True + + Do While condition + Console.WriteLine(index) + index += 1 + If (index = 10) Then + condition = False + End If + Loop + End Sub +End Module +.Value + + Dim expected_transform = + +Module Module1 + Sub Main() + Dim index As Integer = 0 + Dim condition As Boolean = True + + Do + Console.WriteLine(index) + index += 1 + If (index = 10) Then + condition = False + End If + Loop While condition + End Sub +End Module +.Value + + Dim actual_transform = Transforms.Transform(input, TransformKind.DoTopTestToDoBottomTest) + + Assert.Equal(expected_transform, actual_transform) + End Sub + + + Public Sub DoBottomTestToDoTopTestTest() + Dim input As String = + +Module Module1 + Sub Main() + Dim index As Integer = 0 + Dim condition As Boolean = True + + Do + Console.WriteLine(index) + index += 1 + If (index = 10) Then + condition = False + End If + Loop While condition + End Sub +End Module +.Value + + Dim expected_transform = + +Module Module1 + Sub Main() + Dim index As Integer = 0 + Dim condition As Boolean = True + + Do While condition + Console.WriteLine(index) + index += 1 + If (index = 10) Then + condition = False + End If + Loop + End Sub +End Module +.Value + + Dim actual_transform = Transforms.Transform(input, TransformKind.DoBottomTestToDoTopTest) + + Assert.Equal(expected_transform, actual_transform) + End Sub + + + Public Sub WhileToDoWhileTopTestTest() + Dim input As String = + +Module Module1 + Sub Main() + Dim index As Integer = 0 + Dim condition As Boolean = True + + While condition + Console.WriteLine(index) + index += 1 + If (index = 10) Then + condition = False + Exit While + End If + End While + End Sub +End Module +.Value + + Dim expected_transform = + +Module Module1 + Sub Main() + Dim index As Integer = 0 + Dim condition As Boolean = True + + Do While condition + Console.WriteLine(index) + index += 1 + If (index = 10) Then + condition = False + Exit Do + End If + Loop + End Sub +End Module +.Value + + Dim actual_transform = Transforms.Transform(input, TransformKind.WhileToDoWhileTopTest) + + Assert.Equal(expected_transform, actual_transform) + End Sub + + + Public Sub DoWhileTopTestToWhileTest() + Dim input As String = + +Module Module1 + Sub Main() + Dim index As Integer = 0 + Dim condition As Boolean = True + + Do While condition + Console.WriteLine(index) + index += 1 + If (index = 10) Then + condition = False + Exit Do + End If + Loop + End Sub +End Module +.Value + + Dim expected_transform = + +Module Module1 + Sub Main() + Dim index As Integer = 0 + Dim condition As Boolean = True + + While condition + Console.WriteLine(index) + index += 1 + If (index = 10) Then + condition = False + Exit While + End If + End While + End Sub +End Module +.Value + + Dim actual_transform = Transforms.Transform(input, TransformKind.DoWhileTopTestToWhile) + + Assert.Equal(expected_transform, actual_transform) + End Sub + + + Public Sub SingleLineIfToMultiLineIfTest() + Dim input As String = + +Module Module1 + Sub Main() + Dim A, B, C + If True Then A = B + C : B = A + C Else C = A + B : B = A - C + End Sub +End Module +.Value + + ' Dim expected_transform = + ' + 'Module Module1 + ' Sub Main() + ' Dim A, B, C + ' If True Then + ' A = B + C + ' B = A + C + ' Else + ' C = A + B + ' B = A - C + ' End If + ' End Sub + 'End Module + '.Value + Dim expected_transform = vbLf & +"Module Module1" & vbLf & +" Sub Main()" & vbLf & +" Dim A, B, C" & vbLf & +" If True Then A = B + C : B = A + C Else C = A + B : B = A - C" & vbLf & +" End If End Sub" & vbLf & +"End Module" & vbLf + + Dim actual_transform = Transforms.Transform(input, TransformKind.SingleLineIfToMultiLineIf) + + Assert.Equal(expected_transform, actual_transform) + End Sub + +End Class diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Lib/Converter.vb b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Lib/Converter.vb new file mode 100644 index 000000000..04c61495a --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Lib/Converter.vb @@ -0,0 +1,20 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis + +Namespace VisualBasicToCSharpConverter + Partial Public Class Converter + Public Shared Function Convert( + tree As SyntaxTree, + Optional identifierMap As IDictionary(Of String, String) = Nothing, + Optional convertStrings As Boolean = False + ) As SyntaxNode + + Return ConvertTree(tree) + End Function + + Public Shared Function ConvertTree(tree As SyntaxTree) As SyntaxNode + Return New NodeConvertingVisitor().Visit(tree.GetRoot()) + End Function + End Class +End Namespace diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Lib/NodeConvertingVisitor.vb b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Lib/NodeConvertingVisitor.vb new file mode 100644 index 000000000..6ab71243c --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Lib/NodeConvertingVisitor.vb @@ -0,0 +1,2990 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Option Strict Off + +Imports System.Diagnostics.CodeAnalysis +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CSharp.Symbols +Imports Microsoft.CodeAnalysis.CSharp.Syntax +Imports Microsoft.CodeAnalysis.CSharp.SyntaxExtensions +Imports Microsoft.CodeAnalysis.CSharp.SyntaxFactory +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports CS = Microsoft.CodeAnalysis.CSharp +Imports Extension = System.Runtime.CompilerServices.ExtensionAttribute +Imports VB = Microsoft.CodeAnalysis.VisualBasic + +Namespace VisualBasicToCSharpConverter + + Partial Public Class Converter + + Private Class NodeConvertingVisitor + Inherits VisualBasicSyntaxVisitor(Of SyntaxNode) + + Private Shared ReadOnly VoidKeyword As SyntaxToken = Token(CS.SyntaxKind.VoidKeyword) + Private Shared ReadOnly SemicolonToken As SyntaxToken = Token(CS.SyntaxKind.SemicolonToken) + Private Shared ReadOnly MissingSemicolonToken As SyntaxToken = MissingToken(CS.SyntaxKind.SemicolonToken) + ' This is a hack. But because this will be written out to text it'll work. + Private Shared ReadOnly SystemRuntimeInteropServicesCharSetName As CS.Syntax.NameSyntax = ParseName("global::System.Runtime.InteropServices.CharSet") + Private Shared ReadOnly SystemRuntimeInteropServicesCharSetAnsiExpression As CS.Syntax.MemberAccessExpressionSyntax = MemberAccessExpression(CS.SyntaxKind.SimpleMemberAccessExpression, SystemRuntimeInteropServicesCharSetName, IdentifierName("Ansi")) + Private Shared ReadOnly SystemRuntimeInteropServicesCharSetUnicodeExpression As CS.Syntax.MemberAccessExpressionSyntax = MemberAccessExpression(CS.SyntaxKind.SimpleMemberAccessExpression, SystemRuntimeInteropServicesCharSetName, IdentifierName("Unicode")) + Private Shared ReadOnly SystemRuntimeInteropServicesCharSetAutoExpression As CS.Syntax.MemberAccessExpressionSyntax = MemberAccessExpression(CS.SyntaxKind.SimpleMemberAccessExpression, SystemRuntimeInteropServicesCharSetName, IdentifierName("Auto")) + + ' This can change after visiting an OptionStatement. + Private IsOptionExplicitOn As Boolean = True + Private IsOptionCompareBinary As Boolean = True + Private IsOptionStrictOn As Boolean = False + Private IsOptionInferOn As Boolean = True + + Private ReadOnly RootNamespace As String = "" + Private ReadOnly RootNamespaceName As CS.Syntax.NameSyntax = If(String.IsNullOrEmpty(RootNamespace), + Nothing, + ParseName(RootNamespace) + ) + + Protected Function DeriveName(expression As VB.Syntax.ExpressionSyntax) As String + Do While TypeOf expression Is VB.Syntax.InvocationExpressionSyntax + expression = CType(expression, VB.Syntax.InvocationExpressionSyntax).Expression + Loop + + Select Case expression.Kind + Case VB.SyntaxKind.SimpleMemberAccessExpression + + Return CType(expression, VB.Syntax.MemberAccessExpressionSyntax).Name.Identifier.ValueText + + Case VB.SyntaxKind.IdentifierName + + Return CType(expression, VB.Syntax.IdentifierNameSyntax).Identifier.ValueText + + Case VB.SyntaxKind.GenericName + + Return CType(expression, VB.Syntax.GenericNameSyntax).Identifier.ValueText + + Case Else + Return Nothing + End Select + End Function + + Protected Function DeriveRankSpecifiers( + boundsOpt As VB.Syntax.ArgumentListSyntax, + specifiersOpt As IEnumerable(Of VB.Syntax.ArrayRankSpecifierSyntax), + Optional includeSizes As Boolean = False + ) As IEnumerable(Of CS.Syntax.ArrayRankSpecifierSyntax) + + Dim result As New List(Of CS.Syntax.ArrayRankSpecifierSyntax)() + + If boundsOpt IsNot Nothing Then + If includeSizes Then + result.Add(ArrayRankSpecifier(SeparatedList((From arg In boundsOpt.Arguments Select VisitArrayRankSpecifierSize(arg)).Cast(Of CS.Syntax.ExpressionSyntax)))) + Else + result.Add(ArrayRankSpecifier(OmittedArraySizeExpressionList(Of CS.Syntax.ExpressionSyntax)(boundsOpt.Arguments.Count))) + End If + End If + + If specifiersOpt IsNot Nothing Then + For Each ars In specifiersOpt + result.Add(ArrayRankSpecifier(OmittedArraySizeExpressionList(Of CS.Syntax.ExpressionSyntax)(ars.Rank))) + Next + End If + + Return result + + End Function + + Protected Function DeriveInitializer( + identifier As VB.Syntax.ModifiedIdentifierSyntax, + asClauseOpt As VB.Syntax.AsClauseSyntax, + Optional initializerOpt As VB.Syntax.EqualsValueSyntax = Nothing + ) As CS.Syntax.EqualsValueClauseSyntax + + If initializerOpt IsNot Nothing Then + Return Visit(initializerOpt) + End If + + If asClauseOpt IsNot Nothing AndAlso asClauseOpt.IsKind(VB.SyntaxKind.AsNewClause) Then + Dim newExpression = DirectCast(asClauseOpt, VB.Syntax.AsNewClauseSyntax).NewExpression + Select Case newExpression.Kind + Case VB.SyntaxKind.ObjectCreationExpression + Return EqualsValueClause(VisitObjectCreationExpression(newExpression)) + Case VB.SyntaxKind.ArrayCreationExpression + Return EqualsValueClause(VisitArrayCreationExpression(newExpression)) + Case VB.SyntaxKind.AnonymousObjectCreationExpression + Return EqualsValueClause(VisitAnonymousObjectCreationExpression(newExpression)) + End Select + End If + + If identifier.ArrayBounds IsNot Nothing Then + Return EqualsValueClause(ArrayCreationExpression(DeriveType(identifier, asClauseOpt, initializerOpt, includeSizes:=True))) + End If + + Return Nothing + + End Function + + Protected Function DeriveType( + identifier As VB.Syntax.ModifiedIdentifierSyntax, + asClause As VB.Syntax.AsClauseSyntax, + initializer As VB.Syntax.EqualsValueSyntax, + Optional includeSizes As Boolean = False, + Optional isRangeVariable As Boolean = False + ) As CS.Syntax.TypeSyntax + + Dim type = DeriveType(identifier.Identifier, asClause, , initializer, isRangeVariable) + + ' TODO: Implement check for nullable var. + If Not identifier.Nullable.IsKind(VB.SyntaxKind.None) Then + type = NullableType(type) + End If + + If identifier.ArrayBounds IsNot Nothing OrElse + identifier.ArrayRankSpecifiers.Count > 0 Then + + Return ArrayType(type, List(DeriveRankSpecifiers(identifier.ArrayBounds, identifier.ArrayRankSpecifiers, includeSizes))) + End If + + Return type + End Function + + Protected Function DeriveType( + identifier As SyntaxToken, + asClause As VB.Syntax.AsClauseSyntax, + Optional methodKeyword As SyntaxToken = Nothing, + Optional initializerOpt As VB.Syntax.EqualsValueSyntax = Nothing, + Optional isRangeVariable As Boolean = False + ) As CS.Syntax.TypeSyntax + + If asClause IsNot Nothing Then + + If asClause.IsKind(VB.SyntaxKind.AsNewClause) Then + Return IdentifierName("var") + Else + Return Visit(asClause) + End If + + ElseIf methodKeyword.IsKind(VB.SyntaxKind.SubKeyword) Then + + Return PredefinedType(Token(CS.SyntaxKind.VoidKeyword)) + + ElseIf initializerOpt IsNot Nothing AndAlso + IsOptionInferOn AndAlso + (identifier.Parent.Parent.Parent.IsKind(VB.SyntaxKind.LocalDeclarationStatement) OrElse + identifier.Parent.Parent.Parent.IsKind(VB.SyntaxKind.UsingStatement)) Then + + Return IdentifierName("var") + + ElseIf isRangeVariable Then + + ' C# collection range variables omit their type. + Return Nothing + + Else + Dim text = identifier.ToString() + + If Not Char.IsLetterOrDigit(text(text.Length - 1)) Then + + Select Case text(text.Length - 1) + Case "!"c + Return PredefinedType(Token(CS.SyntaxKind.FloatKeyword)) + Case "@"c + Return PredefinedType(Token(CS.SyntaxKind.DecimalKeyword)) + Case "#"c + Return PredefinedType(Token(CS.SyntaxKind.DoubleKeyword)) + Case "$"c + Return PredefinedType(Token(CS.SyntaxKind.StringKeyword)) + Case "%"c + Return PredefinedType(Token(CS.SyntaxKind.IntKeyword)) + Case "&"c + Return PredefinedType(Token(CS.SyntaxKind.LongKeyword)) + End Select + End If + End If + + ' If no AsClause is provided and no type characters are present and this isn't a Sub declaration pick Object or Dynamic based on Option Strict setting. + If IsOptionStrictOn Then + Return PredefinedType(Token(CS.SyntaxKind.ObjectKeyword)) + Else + Return IdentifierName("dynamic") + End If + + End Function + + Protected Function DeriveType(declarator As VB.Syntax.CollectionRangeVariableSyntax) As CS.Syntax.TypeSyntax + + Return DeriveType(declarator.Identifier, declarator.AsClause, initializer:=Nothing, isRangeVariable:=True) + + End Function + + Protected Function TransferTrivia(source As SyntaxNode, target As SyntaxNode) As SyntaxNode + + Return target.WithTrivia(VisitTrivia(source.GetLeadingTrivia()), VisitTrivia(source.GetTrailingTrivia())) + + End Function + + Public Overloads Function Visit(nodes As IEnumerable(Of SyntaxNode)) As IEnumerable(Of SyntaxNode) + + Return From node In nodes Select Visit(node) + + End Function + + Public Overloads Function Visit(statements As IEnumerable(Of VB.Syntax.StatementSyntax)) As IEnumerable(Of SyntaxNode) + + ' VB variable declarations allow multiple types and variables. In order to translate to proper C# code + ' we have to flatten the list here by raising each declarator to the level of its parent. + Return Aggregate + node In statements + Into + SelectMany(Flatten(node)) + + + End Function + + ' TODO: This suppression should be removed once we have rulesets in place for Roslyn.sln + + Function Flatten(statement As SyntaxNode) As IEnumerable(Of SyntaxNode) + + Select Case statement.Kind + Case VB.SyntaxKind.FieldDeclaration + Return Aggregate node In CType(statement, VB.Syntax.FieldDeclarationSyntax).Declarators Into SelectMany(VisitVariableDeclaratorVariables(node)) + Case VB.SyntaxKind.LocalDeclarationStatement + Return Aggregate node In CType(statement, VB.Syntax.LocalDeclarationStatementSyntax).Declarators Into SelectMany(VisitVariableDeclaratorVariables(node)) + Case Else + Return {Visit(statement)} + End Select + + End Function + + Public Overrides Function VisitAccessorStatement(node As VB.Syntax.AccessorStatementSyntax) As SyntaxNode + + Dim accessorBlock As VB.Syntax.AccessorBlockSyntax = node.Parent + + Dim kind As CS.SyntaxKind + Select Case node.Kind + Case VB.SyntaxKind.GetAccessorStatement + kind = CS.SyntaxKind.GetAccessorDeclaration + Case VB.SyntaxKind.SetAccessorStatement + kind = CS.SyntaxKind.SetAccessorDeclaration + Case VB.SyntaxKind.AddHandlerAccessorStatement + kind = CS.SyntaxKind.AddAccessorDeclaration + Case VB.SyntaxKind.RemoveHandlerAccessorStatement + kind = CS.SyntaxKind.RemoveAccessorDeclaration + Case VB.SyntaxKind.RaiseEventAccessorStatement + + ' TODO: Transform RaiseEvent accessor into a method. + Throw New NotImplementedException() + + Case Else + Throw New NotSupportedException(node.Kind.ToString()) + End Select + + Return TransferTrivia(accessorBlock, AccessorDeclaration(kind).WithAttributeLists(List(VisitAttributeLists(node.AttributeLists))).WithModifiers(TokenList(VisitModifiers(node.Modifiers))).WithBody(Block(List(Visit(accessorBlock.Statements))))) + + End Function + + Public Overrides Function VisitAddRemoveHandlerStatement(node As VB.Syntax.AddRemoveHandlerStatementSyntax) As SyntaxNode + + If node.IsKind(VB.SyntaxKind.AddHandlerStatement) Then + Return TransferTrivia(node, ExpressionStatement(AssignmentExpression(CS.SyntaxKind.AddAssignmentExpression, Visit(node.EventExpression), Visit(node.DelegateExpression)))) + Else + Return TransferTrivia(node, ExpressionStatement(AssignmentExpression(CS.SyntaxKind.SubtractAssignmentExpression, Visit(node.EventExpression), Visit(node.DelegateExpression)))) + End If + + End Function + + Public Overrides Function VisitAnonymousObjectCreationExpression(node As VB.Syntax.AnonymousObjectCreationExpressionSyntax) As SyntaxNode + + Return AnonymousObjectCreationExpression(SeparatedList(Visit(node.Initializer.Initializers).Cast(Of CS.Syntax.AnonymousObjectMemberDeclaratorSyntax))) + + End Function + + Public Overrides Function VisitArgumentList(node As VB.Syntax.ArgumentListSyntax) As SyntaxNode + + If node Is Nothing Then Return ArgumentList() + + Return ArgumentList(SeparatedList(Visit(node.Arguments).Cast(Of CS.Syntax.ArgumentSyntax))) + + End Function + + Public Overrides Function VisitArrayCreationExpression(node As VB.Syntax.ArrayCreationExpressionSyntax) As SyntaxNode + + Return ArrayCreationExpression(ArrayType(Visit(node.Type)) _ + .WithRankSpecifiers(List(DeriveRankSpecifiers(node.ArrayBounds, node.RankSpecifiers, includeSizes:=True)))) _ + .WithInitializer(If(node.ArrayBounds IsNot Nothing AndAlso node.Initializer.Initializers.Count = 0, Nothing, VisitCollectionInitializer(node.Initializer))) + + End Function + + Public Overrides Function VisitArrayRankSpecifier(node As VB.Syntax.ArrayRankSpecifierSyntax) As SyntaxNode + + Return ArrayRankSpecifier(OmittedArraySizeExpressionList(Of CS.Syntax.ExpressionSyntax)(node.Rank)) + + End Function + + Protected Overloads Function VisitArrayRankSpecifierSize(node As VB.Syntax.ArgumentSyntax) As SyntaxNode + + If node Is Nothing Then Return Nothing + + If TypeOf node Is VB.Syntax.RangeArgumentSyntax Then + Dim arg As VB.Syntax.RangeArgumentSyntax = node + + Return VisitArrayBound(arg.UpperBound) + Else + Return VisitArrayBound(CType(node, VB.Syntax.SimpleArgumentSyntax).Expression) + End If + + End Function + + Protected Function VisitArrayBound(expression As SyntaxNode) As SyntaxNode + + If expression.Kind = VB.SyntaxKind.SubtractExpression Then + Dim be As VB.Syntax.BinaryExpressionSyntax = expression + + If be.Right.Kind = VB.SyntaxKind.NumericLiteralExpression Then + If CInt(CType(be.Right, VB.Syntax.LiteralExpressionSyntax).Token.Value) = 1 Then + Return Visit(be.Left) + End If + End If + + ElseIf expression.Kind = VB.SyntaxKind.NumericLiteralExpression Then + + ' Practically speaking this can only legally be -1. + Dim length = CInt(CType(expression, VB.Syntax.LiteralExpressionSyntax).Token.Value) + 1 + + Return LiteralExpression(CS.SyntaxKind.NumericLiteralExpression, Literal(CStr(length), length)) + + ElseIf expression.IsKind(VB.SyntaxKind.UnaryMinusExpression) Then + + Dim negate As VB.Syntax.UnaryExpressionSyntax = expression + If negate.Operand.IsKind(VB.SyntaxKind.NumericLiteralExpression) Then + Dim length = -CInt(CType(negate.Operand, VB.Syntax.LiteralExpressionSyntax).Token.Value) + 1 + + Return LiteralExpression(CS.SyntaxKind.NumericLiteralExpression, Literal(CStr(length), length)) + End If + + End If + + Return BinaryExpression( + CS.SyntaxKind.AddExpression, + ParenthesizedExpression(Visit(expression)), + LiteralExpression(CS.SyntaxKind.NumericLiteralExpression, Literal("1", 1)) + ) + + End Function + + Public Overrides Function VisitArrayType(node As VB.Syntax.ArrayTypeSyntax) As SyntaxNode + + Return ArrayType(Visit(node.ElementType), List(DeriveRankSpecifiers(Nothing, node.RankSpecifiers.ToList()))) + + End Function + + Public Overrides Function VisitAssignmentStatement(node As VB.Syntax.AssignmentStatementSyntax) As SyntaxNode + + Return TransferTrivia(node, ExpressionStatement(AssignmentExpression(CS.SyntaxKind.SimpleAssignmentExpression, Visit(node.Left), Visit(node.Right)))) + + End Function + + Public Overrides Function VisitAttribute(node As VB.Syntax.AttributeSyntax) As SyntaxNode + + Return TransferTrivia(node.Parent, AttributeList(SingletonSeparatedList(Attribute(Visit(node.Name), VisitAttributeArgumentList(node.ArgumentList)))).WithTarget(VisitAttributeTarget(node.Target))) + + End Function + + Protected Function VisitAttributeArgumentList(node As VB.Syntax.ArgumentListSyntax) As SyntaxNode + + If node Is Nothing Then Return Nothing + + Return AttributeArgumentList(SeparatedList(Visit(node.Arguments).Cast(Of CS.Syntax.AttributeArgumentSyntax))) + + End Function + + Public Overrides Function VisitAttributeList(node As VB.Syntax.AttributeListSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Protected Function VisitAttributeLists(nodes As IEnumerable(Of VB.Syntax.AttributeListSyntax)) As IEnumerable(Of SyntaxNode) + + Return Visit((From list In nodes, attribute In list.Attributes Select attribute)) + + End Function + + Public Overrides Function VisitAttributesStatement(node As VB.Syntax.AttributesStatementSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Protected Function VisitAttributeStatements(statements As IEnumerable(Of VB.Syntax.AttributesStatementSyntax)) As IEnumerable(Of SyntaxNode) + + ' TOOD: AttributeStatement contains a list of blocks but there is only ever one block in the list. + Return Visit((From statement In statements, list In statement.AttributeLists, attribute In list.Attributes Select attribute)) + + End Function + + Public Overrides Function VisitAttributeTarget(node As VB.Syntax.AttributeTargetSyntax) As SyntaxNode + + If node Is Nothing Then Return Nothing + + If node.AttributeModifier.IsKind(VB.SyntaxKind.AssemblyKeyword) Then + Return AttributeTargetSpecifier(Token(CS.SyntaxKind.AssemblyKeyword)) + Else + Return AttributeTargetSpecifier(Token(CS.SyntaxKind.ModuleKeyword)) + End If + + End Function + + Public Overrides Function VisitAwaitExpression(node As VB.Syntax.AwaitExpressionSyntax) As SyntaxNode + + Return AwaitExpression(Visit(node.Expression)) + + End Function + + Public Overrides Function VisitBadDirectiveTrivia(node As VB.Syntax.BadDirectiveTriviaSyntax) As SyntaxNode + + Throw New NotImplementedException(node.ToString()) + + End Function + + Public Overrides Function VisitBinaryConditionalExpression(node As VB.Syntax.BinaryConditionalExpressionSyntax) As SyntaxNode + + Return BinaryExpression(CS.SyntaxKind.CoalesceExpression, Visit(node.FirstExpression), Visit(node.SecondExpression)) + + End Function + + Public Overrides Function VisitBinaryExpression(node As VB.Syntax.BinaryExpressionSyntax) As SyntaxNode + + Dim kind As CS.SyntaxKind + + Select Case node.Kind + Case VB.SyntaxKind.AddExpression + kind = CS.SyntaxKind.AddExpression + Case VB.SyntaxKind.SubtractExpression + kind = CS.SyntaxKind.SubtractExpression + Case VB.SyntaxKind.MultiplyExpression + kind = CS.SyntaxKind.MultiplyExpression + Case VB.SyntaxKind.DivideExpression + + kind = CS.SyntaxKind.DivideExpression + ' TODO: Transform into cast with division if needed. + + Case VB.SyntaxKind.ModuloExpression + kind = CS.SyntaxKind.ModuloExpression + + Case VB.SyntaxKind.IntegerDivideExpression + + kind = CS.SyntaxKind.DivideExpression + ' TODO: Transform into user-defined operator method call if needed. + + Case VB.SyntaxKind.ExponentiateExpression + + 'TODO: Transform into call to Math.Pow. + Return ExpressionStatement( + InvocationExpression( + ParseName("global::System.Math.Pow"), + ArgumentList( + SeparatedList({ + CS.SyntaxFactory.Argument(Visit(node.Left)), + CS.SyntaxFactory.Argument(Visit(node.Right))} + ) + ) + ) + ) + + Return NotImplementedExpression(node) + + Throw New NotImplementedException(node.ToString()) + + Case VB.SyntaxKind.EqualsExpression + kind = CS.SyntaxKind.EqualsExpression + Case VB.SyntaxKind.NotEqualsExpression + kind = CS.SyntaxKind.NotEqualsExpression + Case VB.SyntaxKind.LessThanExpression + kind = CS.SyntaxKind.LessThanExpression + Case VB.SyntaxKind.LessThanOrEqualExpression + kind = CS.SyntaxKind.LessThanOrEqualExpression + Case VB.SyntaxKind.GreaterThanExpression + kind = CS.SyntaxKind.GreaterThanExpression + Case VB.SyntaxKind.GreaterThanOrEqualExpression + kind = CS.SyntaxKind.GreaterThanOrEqualExpression + + Case VB.SyntaxKind.IsExpression + + ' TODO: Transform into call to Object.ReferenceEquals as necessary. + kind = CS.SyntaxKind.EqualsExpression + + Case VB.SyntaxKind.IsNotExpression + + ' TODO: Transform into NotExpression of call to Object.ReferenceEquals as necessary. + kind = CS.SyntaxKind.NotEqualsExpression + + Case VB.SyntaxKind.LeftShiftExpression + kind = CS.SyntaxKind.LeftShiftExpression + Case VB.SyntaxKind.RightShiftExpression + kind = CS.SyntaxKind.RightShiftExpression + Case VB.SyntaxKind.AndExpression + kind = CS.SyntaxKind.BitwiseAndExpression + Case VB.SyntaxKind.AndAlsoExpression + kind = CS.SyntaxKind.LogicalAndExpression + Case VB.SyntaxKind.OrExpression + kind = CS.SyntaxKind.BitwiseOrExpression + Case VB.SyntaxKind.OrElseExpression + kind = CS.SyntaxKind.LogicalOrExpression + Case VB.SyntaxKind.ExclusiveOrExpression + kind = CS.SyntaxKind.ExclusiveOrExpression + + Case VB.SyntaxKind.ConcatenateExpression + + kind = CS.SyntaxKind.AddExpression + + ' TODO: Transform into call to user-defined operator if needed (e.g. for user-defined operator). + + Case VB.SyntaxKind.LikeExpression + + Return NotImplementedExpression(node) + + Throw New NotSupportedException(node.Kind.ToString()) + + Case Else + + Return NotImplementedExpression(node) + + Throw New NotSupportedException(node.Kind.ToString()) + End Select + + Return BinaryExpression(kind, Visit(node.Left), Visit(node.Right)) + End Function + + Public Overrides Function VisitCallStatement(node As VB.Syntax.CallStatementSyntax) As SyntaxNode + + Return TransferTrivia(node, ExpressionStatement(VisitInvocationExpression(node.Invocation))) + + End Function + + Public Overrides Function VisitExpressionStatement(node As VB.Syntax.ExpressionStatementSyntax) As SyntaxNode + + Return TransferTrivia(node, ExpressionStatement(Visit(node.Expression))) + + End Function + + Public Overrides Function VisitCaseBlock(node As VB.Syntax.CaseBlockSyntax) As SyntaxNode + + Dim statements = Visit(node.Statements) + If Not node.IsKind(VB.SyntaxKind.CaseElseBlock) Then + statements = statements.Union({BreakStatement()}) + End If + + Return TransferTrivia(node, SwitchSection( + List(Visit(node.CaseStatement.Cases)), + List(statements) + ) + ) + + End Function + + Protected Function VisitCaseBlocks(blocks As IEnumerable(Of VB.Syntax.CaseBlockSyntax)) As IEnumerable(Of SyntaxNode) + + Return From b In blocks Select VisitCaseBlock(b) + + End Function + + Public Overrides Function VisitElseCaseClause(node As VB.Syntax.ElseCaseClauseSyntax) As SyntaxNode + + Return DefaultSwitchLabel() + + End Function + + Public Overrides Function VisitRangeCaseClause(node As VB.Syntax.RangeCaseClauseSyntax) As SyntaxNode + + Return CaseSwitchLabel(NotImplementedExpression(node)) + + ' TODO: Rewrite this to an if statement. + Throw New NotImplementedException(node.ToString()) + + End Function + + Public Overrides Function VisitRelationalCaseClause(node As VB.Syntax.RelationalCaseClauseSyntax) As SyntaxNode + + Return CaseSwitchLabel(NotImplementedExpression(node)) + + ' TODO: Rewrite this to an if statement. + Throw New NotImplementedException(node.ToString()) + + End Function + + Public Overrides Function VisitCaseStatement(node As VB.Syntax.CaseStatementSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Public Overrides Function VisitSimpleCaseClause(node As VB.Syntax.SimpleCaseClauseSyntax) As SyntaxNode + + Return CaseSwitchLabel(Visit(node.Value)) + + End Function + + Public Overrides Function VisitDirectCastExpression(node As VB.Syntax.DirectCastExpressionSyntax) As SyntaxNode + Return ParenthesizedExpression(CastExpression(Visit(node.Type), Visit(node.Expression))) + End Function + + Public Overrides Function VisitCTypeExpression(node As VB.Syntax.CTypeExpressionSyntax) As SyntaxNode + Return ParenthesizedExpression(CastExpression(Visit(node.Type), Visit(node.Expression))) + End Function + + Public Overrides Function VisitTryCastExpression(node As VB.Syntax.TryCastExpressionSyntax) As SyntaxNode + Return ParenthesizedExpression(BinaryExpression(CS.SyntaxKind.AsExpression, Visit(node.Expression), Visit(node.Type))) + End Function + + Public Overrides Function VisitCatchFilterClause(node As VB.Syntax.CatchFilterClauseSyntax) As SyntaxNode + + ' We could in theory translate this into a switch inside a catch. + ' It's not really at all the same thing as a filter though so for now + ' we'll just throw. + Throw New NotSupportedException(node.Kind.ToString()) + + End Function + + Public Overrides Function VisitCatchBlock(node As VB.Syntax.CatchBlockSyntax) As SyntaxNode + + Return CatchClause().WithDeclaration(VisitCatchStatement(node.CatchStatement)).WithBlock(Block(List(Visit(node.Statements)))) + + End Function + + Public Overrides Function VisitCatchStatement(node As VB.Syntax.CatchStatementSyntax) As SyntaxNode + + + If node.IdentifierName Is Nothing Then Return Nothing + + Dim result = CatchDeclaration(VisitSimpleAsClause(node.AsClause)).WithIdentifier(VisitIdentifier(node.IdentifierName.Identifier)) + + If node.WhenClause IsNot Nothing Then result = result.WithTrailingTrivia({Comment("/* " & node.WhenClause.ToString() & " */")}) + + Return result + + End Function + + Protected Function VisitCatchBlocks(parts As IEnumerable(Of VB.Syntax.CatchBlockSyntax)) As IEnumerable(Of SyntaxNode) + + Return From part In parts Select VisitCatchBlock(part) + + End Function + + Public Overrides Function VisitCollectionInitializer(node As VB.Syntax.CollectionInitializerSyntax) As SyntaxNode + + If node Is Nothing Then Return Nothing + + Select Case node.Parent.Kind + Case VB.SyntaxKind.ObjectCollectionInitializer, + VB.SyntaxKind.AsNewClause + Return InitializerExpression(CS.SyntaxKind.CollectionInitializerExpression, SeparatedList(Visit(node.Initializers).Cast(Of CS.Syntax.ExpressionSyntax))) + + Case VB.SyntaxKind.ArrayCreationExpression + Return InitializerExpression(CS.SyntaxKind.ArrayInitializerExpression, SeparatedList(Visit(node.Initializers).Cast(Of CS.Syntax.ExpressionSyntax))) + + Case Else + + ' This covers array initializers in a variable declaration. + If node.Parent.IsKind(VB.SyntaxKind.EqualsValue) AndAlso + node.Parent.Parent.IsKind(VB.SyntaxKind.VariableDeclarator) AndAlso + CType(node.Parent.Parent, VB.Syntax.VariableDeclaratorSyntax).AsClause IsNot Nothing Then + + Return InitializerExpression(CS.SyntaxKind.ArrayInitializerExpression, SeparatedList(Visit(node.Initializers).Cast(Of CS.Syntax.ExpressionSyntax))) + End If + + ' This is an array literal. + ' TODO: Calculate the rank of this array initializer, right now it assumes rank 1. + Return ImplicitArrayCreationExpression( + InitializerExpression(CS.SyntaxKind.ArrayInitializerExpression, SeparatedList(Visit(node.Initializers).Cast(Of CS.Syntax.ExpressionSyntax))) + ) + End Select + + End Function + + Public Overrides Function VisitCollectionRangeVariable(node As VB.Syntax.CollectionRangeVariableSyntax) As SyntaxNode + Return MyBase.VisitCollectionRangeVariable(node) + End Function + + Public Overrides Function VisitCompilationUnit(node As VB.Syntax.CompilationUnitSyntax) As SyntaxNode + Dim usings = List(VisitImportsStatements(node.Imports)) + Dim attributes = List(VisitAttributeStatements(node.Attributes)) + Dim members = List(VisitMembers(node.Members)) + Dim root = CompilationUnit().WithUsings(usings).WithAttributeLists(attributes).WithMembers(members) + Return root.NormalizeWhitespace() + End Function + + Public Overrides Function VisitConstDirectiveTrivia(node As VB.Syntax.ConstDirectiveTriviaSyntax) As SyntaxNode + + If node.Value.IsKind(VB.SyntaxKind.TrueLiteralExpression) OrElse + node.Value.IsKind(VB.SyntaxKind.FalseLiteralExpression) Then + + Return DefineDirectiveTrivia(VisitIdentifier(node.Name), isActive:=True) + Else + Return BadDirectiveTrivia(MissingToken(CS.SyntaxKind.HashToken).WithTrailingTrivia(TriviaList(Comment("/* " & node.ToString() & " */"))), isActive:=True) + + Throw New NotSupportedException("Non-boolean directive constants.") + End If + + End Function + + Public Overrides Function VisitSubNewStatement(node As VB.Syntax.SubNewStatementSyntax) As SyntaxNode + + Dim typeName = CType(node.Parent.Parent, VB.Syntax.TypeBlockSyntax).BlockStatement.Identifier + + Dim subNewBlock As VB.Syntax.ConstructorBlockSyntax = node.Parent + + Dim initializer As CS.Syntax.ConstructorInitializerSyntax = Nothing + + ' Check for chained constructor call. + If subNewBlock.Statements.Count >= 1 Then + Dim firstStatement = subNewBlock.Statements(0) + Dim invocationExpression As VB.Syntax.InvocationExpressionSyntax + + Select Case firstStatement.Kind + Case VB.SyntaxKind.CallStatement + invocationExpression = TryCast(DirectCast(firstStatement, VB.Syntax.CallStatementSyntax).Invocation, VB.Syntax.InvocationExpressionSyntax) + Case VB.SyntaxKind.ExpressionStatement + invocationExpression = TryCast(DirectCast(firstStatement, VB.Syntax.ExpressionStatementSyntax).Expression, VB.Syntax.InvocationExpressionSyntax) + Case Else + invocationExpression = Nothing + End Select + + If invocationExpression IsNot Nothing Then + Dim memberAccess = TryCast(invocationExpression.Expression, VB.Syntax.MemberAccessExpressionSyntax) + If memberAccess IsNot Nothing Then + + If TypeOf memberAccess.Expression Is VB.Syntax.InstanceExpressionSyntax AndAlso + memberAccess.Name.Identifier.ToString().Equals("New", StringComparison.OrdinalIgnoreCase) Then + + Select Case memberAccess.Expression.Kind + Case VB.SyntaxKind.MeExpression, VB.SyntaxKind.MyClassExpression + initializer = ConstructorInitializer(CS.SyntaxKind.ThisConstructorInitializer, VisitArgumentList(invocationExpression.ArgumentList)) + Case VB.SyntaxKind.MyBaseExpression + initializer = ConstructorInitializer(CS.SyntaxKind.BaseConstructorInitializer, VisitArgumentList(invocationExpression.ArgumentList)) + End Select + End If + End If + End If + End If + + ' TODO: Fix trivia transfer so that trailing trivia on this node doesn't end up on the close curly. + ' TODO: Implement trivia transfer so that trivia on the Me.New or MyBase.New call is not lost. + Return TransferTrivia(node, ConstructorDeclaration(VisitIdentifier(typeName)) _ + .WithAttributeLists(List(VisitAttributeLists(node.AttributeLists))) _ + .WithModifiers(TokenList(VisitModifiers(node.Modifiers))) _ + .WithParameterList(VisitParameterList(node.ParameterList)) _ + .WithInitializer(initializer) _ + .WithBody(Block(List(Visit(If(initializer Is Nothing, subNewBlock.Statements, subNewBlock.Statements.Skip(1)))))) + ) + + End Function + + Public Overrides Function VisitContinueStatement(node As VB.Syntax.ContinueStatementSyntax) As SyntaxNode + + ' So long as this continue statement binds to its immediately enclosing loop this is simple. + ' Otherwise it would require rewriting with goto statements. + ' TODO: Consider implementing this using binding instead. + Dim parent = node.Parent + Do Until VB.SyntaxFacts.IsDoLoopBlock(parent.Kind) OrElse + parent.IsKind(VB.SyntaxKind.ForBlock) OrElse + parent.IsKind(VB.SyntaxKind.ForEachBlock) OrElse + parent.IsKind(VB.SyntaxKind.WhileBlock) + + parent = parent.Parent + Loop + + If (node.IsKind(VB.SyntaxKind.ContinueDoStatement) AndAlso VB.SyntaxFacts.IsDoLoopBlock(parent.Kind)) OrElse + (node.IsKind(VB.SyntaxKind.ContinueForStatement) AndAlso (parent.IsKind(VB.SyntaxKind.ForBlock) OrElse parent.IsKind(VB.SyntaxKind.ForEachBlock))) OrElse + (node.IsKind(VB.SyntaxKind.ContinueWhileStatement) AndAlso parent.IsKind(VB.SyntaxKind.WhileBlock)) Then + + Return ContinueStatement() + Else + + Return NotImplementedStatement(node) + + Throw New NotImplementedException("Rewriting Continue statements which branch out of their immediately containing loop block into gotos.") + End If + + End Function + + Public Overrides Function VisitDeclareStatement(node As VB.Syntax.DeclareStatementSyntax) As SyntaxNode + ' Declare Ansi|Unicode|Auto Sub|Function Name Lib "LibName" Alias "AliasName"(ParameterList)[As ReturnType] + ' [DllImport("LibName", CharSet: CharSet.Ansi|Unicode|Auto, EntryPoint: AliasName|Name)] + ' extern ReturnType|void Name(ParameterList); + + Dim charSet As CS.Syntax.ExpressionSyntax = Nothing + If node.CharsetKeyword.IsKind(VB.SyntaxKind.None) Then + charSet = SystemRuntimeInteropServicesCharSetAutoExpression + Else + Select Case node.CharsetKeyword.Kind + Case VB.SyntaxKind.AnsiKeyword + charSet = SystemRuntimeInteropServicesCharSetAnsiExpression + Case VB.SyntaxKind.UnicodeKeyword + charSet = SystemRuntimeInteropServicesCharSetUnicodeExpression + Case VB.SyntaxKind.AutoKeyword + charSet = SystemRuntimeInteropServicesCharSetAutoExpression + End Select + End If + + Dim aliasString As String + If node.AliasKeyword.IsKind(VB.SyntaxKind.None) Then + aliasString = node.Identifier.ValueText + Else + aliasString = node.AliasName.Token.ValueText + End If + + + Dim dllImportAttribute = Attribute( + ParseName("global::System.Runtime.InteropServices.DllImport"), + AttributeArgumentList(SeparatedList({ + AttributeArgument(LiteralExpression(CS.SyntaxKind.StringLiteralExpression, Literal(node.LibraryName.Token.ToString(), node.LibraryName.Token.ValueText))), + AttributeArgument(charSet).WithNameColon(NameColon(IdentifierName("CharSet"))), + AttributeArgument( + LiteralExpression(CS.SyntaxKind.StringLiteralExpression, Literal("""" & aliasString & """", aliasString))).WithNameColon(NameColon(IdentifierName("EntryPoint")))} + ) + ) + ) + + ' TODO: Transfer attributes on the return type to the statement. + Return MethodDeclaration(DeriveType(node.Identifier, node.AsClause, node.SubOrFunctionKeyword), VisitIdentifier(node.Identifier)) _ + .WithAttributeLists(List(VisitAttributeLists(node.AttributeLists).Union({AttributeList(SingletonSeparatedList(dllImportAttribute))}))) _ + .WithModifiers(TokenList(VisitModifiers(node.Modifiers).Union({Token(CS.SyntaxKind.ExternKeyword)}))) _ + .WithParameterList(VisitParameterList(node.ParameterList)) + + End Function + + Public Overrides Function VisitDelegateStatement(node As VB.Syntax.DelegateStatementSyntax) As SyntaxNode + + Return DelegateDeclaration( + DeriveType(node.Identifier, node.AsClause, node.SubOrFunctionKeyword), + VisitIdentifier(node.Identifier)) _ + .WithAttributeLists(List(VisitAttributeLists(node.AttributeLists))) _ + .WithModifiers(TokenList(VisitModifiers(node.Modifiers))) _ + .WithTypeParameterList(VisitTypeParameterList(node.TypeParameterList)) _ + .WithParameterList(VisitParameterList(node.ParameterList)) _ + .WithConstraintClauses(List(VisitTypeParameterConstraintClauses(node.TypeParameterList))) + + End Function + + Public Overrides Function VisitDocumentationCommentTrivia(node As VB.Syntax.DocumentationCommentTriviaSyntax) As SyntaxNode + Return DocumentationCommentTrivia(CS.SyntaxKind.SingleLineDocumentationCommentTrivia).WithEndOfComment(MissingToken(CS.SyntaxKind.EndOfDocumentationCommentToken).WithLeadingTrivia(TriviaList(Comment("/* " & node.ToString() & " */")))) + End Function + + Public Overrides Function VisitDoLoopBlock(node As VB.Syntax.DoLoopBlockSyntax) As SyntaxNode + + Select Case node.Kind + Case VB.SyntaxKind.DoWhileLoopBlock, VB.SyntaxKind.DoUntilLoopBlock + + Return WhileStatement(VisitWhileOrUntilClause(node.DoStatement.WhileOrUntilClause), Block(List(Visit(node.Statements)))) + + Case VB.SyntaxKind.DoLoopWhileBlock, VB.SyntaxKind.DoLoopUntilBlock + + Return DoStatement(Block(List(Visit(node.Statements))), VisitWhileOrUntilClause(node.LoopStatement.WhileOrUntilClause)) + + Case VB.SyntaxKind.SimpleDoLoopBlock + + Return WhileStatement(LiteralExpression(CS.SyntaxKind.TrueLiteralExpression), Block(List(Visit(node.Statements)))) + + Case Else + Throw New NotSupportedException(node.Kind.ToString()) + End Select + + End Function + + Public Overrides Function VisitDoStatement(node As VB.Syntax.DoStatementSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Public Overrides Function VisitElseDirectiveTrivia(node As VB.Syntax.ElseDirectiveTriviaSyntax) As SyntaxNode + + Return ElseDirectiveTrivia(isActive:=True, branchTaken:=False) + + End Function + + Public Overrides Function VisitElseBlock(node As VB.Syntax.ElseBlockSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Public Overrides Function VisitElseStatement(node As VB.Syntax.ElseStatementSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Public Overrides Function VisitEmptyStatement(node As VB.Syntax.EmptyStatementSyntax) As SyntaxNode + + Return TransferTrivia(node, EmptyStatement()) + + End Function + + Public Overrides Function VisitEndBlockStatement(node As VB.Syntax.EndBlockStatementSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Public Overrides Function VisitEndExternalSourceDirectiveTrivia(node As VB.Syntax.EndExternalSourceDirectiveTriviaSyntax) As SyntaxNode + + Return LineDirectiveTrivia(MissingToken(CS.SyntaxKind.NumericLiteralToken), isActive:=False) + + End Function + + Public Overrides Function VisitEndIfDirectiveTrivia(node As VB.Syntax.EndIfDirectiveTriviaSyntax) As SyntaxNode + + Return EndIfDirectiveTrivia(isActive:=False) + + End Function + + Public Overrides Function VisitEndRegionDirectiveTrivia(node As VB.Syntax.EndRegionDirectiveTriviaSyntax) As SyntaxNode + + Return EndRegionDirectiveTrivia(isActive:=False) + + End Function + + Public Overrides Function VisitEnumBlock(node As VB.Syntax.EnumBlockSyntax) As SyntaxNode + + Return VisitEnumStatement(node.EnumStatement) + + End Function + + Public Overrides Function VisitEnumMemberDeclaration(node As VB.Syntax.EnumMemberDeclarationSyntax) As SyntaxNode + + Return TransferTrivia(node, EnumMemberDeclaration(List(VisitAttributeLists(node.AttributeLists)), VisitIdentifier(node.Identifier), VisitEqualsValue(node.Initializer))) + + End Function + + Public Overrides Function VisitEnumStatement(node As VB.Syntax.EnumStatementSyntax) As SyntaxNode + + Dim enumBlock As VB.Syntax.EnumBlockSyntax = node.Parent + + Dim base As CS.Syntax.BaseListSyntax = Nothing + If node.UnderlyingType IsNot Nothing Then + base = BaseList(SingletonSeparatedList(Of BaseTypeSyntax)(SimpleBaseType(CType(VisitSimpleAsClause(node.UnderlyingType), CS.Syntax.TypeSyntax)))) + End If + + Return TransferTrivia(enumBlock, EnumDeclaration(VisitIdentifier(node.Identifier)) _ + .WithAttributeLists(List(VisitAttributeLists(node.AttributeLists))) _ + .WithModifiers(TokenList(VisitModifiers(node.Modifiers))) _ + .WithBaseList(base) _ + .WithMembers(SeparatedList(Visit(enumBlock.Members).Cast(Of CS.Syntax.EnumMemberDeclarationSyntax))) + ) + + End Function + + Public Overrides Function VisitEqualsValue(node As VB.Syntax.EqualsValueSyntax) As SyntaxNode + + If node Is Nothing Then Return Nothing + + Return EqualsValueClause(Visit(node.Value)) + + End Function + + Public Overrides Function VisitEraseStatement(node As VB.Syntax.EraseStatementSyntax) As SyntaxNode + + Return NotImplementedStatement(node) + + ' TODO: Implement rewrite to call Array.Clear. + Throw New NotImplementedException(node.ToString()) + + End Function + + Public Overrides Function VisitErrorStatement(node As VB.Syntax.ErrorStatementSyntax) As SyntaxNode + + Return NotImplementedStatement(node) + Throw New NotSupportedException(node.Kind.ToString()) + + End Function + + Public Overrides Function VisitEventBlock(node As VB.Syntax.EventBlockSyntax) As SyntaxNode + + Return VisitEventStatement(node.EventStatement) + + End Function + + Public Overrides Function VisitEventStatement(node As VB.Syntax.EventStatementSyntax) As SyntaxNode + + Dim eventBlock = TryCast(node.Parent, VB.Syntax.EventBlockSyntax) + + Dim accessors = If(eventBlock Is Nothing, + Nothing, + eventBlock.Accessors + ) + + If node.AsClause IsNot Nothing Then + If accessors.Count = 0 Then + ' TODO: Synthesize an explicit interface implementation if this event's name differs from the name of the method in its Implements clause. + Return TransferTrivia(node, EventFieldDeclaration( + VariableDeclaration( + VisitSimpleAsClause(node.AsClause), + SingletonSeparatedList(VariableDeclarator(VisitIdentifier(node.Identifier))) + )).WithAttributeLists(List(VisitAttributeLists(node.AttributeLists))) _ + .WithModifiers(TokenList(VisitModifiers(node.Modifiers))) + ) + + Else + Return NotImplementedMember(node) + + Return TransferTrivia(eventBlock, EventDeclaration( + VisitSimpleAsClause(node.AsClause), + VisitIdentifier(node.Identifier)) _ + .WithAttributeLists(List(VisitAttributeLists(node.AttributeLists))) _ + .WithModifiers(TokenList(VisitModifiers(node.Modifiers))) _ + .WithAccessorList(AccessorList(List(Visit(eventBlock.Accessors)))) + ) + End If + Else + Return NotImplementedMember(node) + + ' TODO: Implement rewrite to add implicit delegate declaration. + Throw New NotSupportedException("Events with inline parameter lists.") + End If + + End Function + + Public Overrides Function VisitExitStatement(node As VB.Syntax.ExitStatementSyntax) As SyntaxNode + + Select Case node.Kind + Case VB.SyntaxKind.ExitSubStatement, + VB.SyntaxKind.ExitOperatorStatement + + Return ReturnStatement() + + Case VB.SyntaxKind.ExitTryStatement + + Return NotImplementedStatement(node) + ' TODO: Implement a rewrite of this to a goto statement. + Throw New NotSupportedException(node.Kind.ToString()) + + Case VB.SyntaxKind.ExitSelectStatement + + ' TODO: Implement rewrite to goto statement here if there are intermediate loops between this statement and the Select block. + Return BreakStatement() + + Case VB.SyntaxKind.ExitPropertyStatement + + Dim parent = node.Parent + Do Until TypeOf parent Is VB.Syntax.MethodBaseSyntax + parent = parent.Parent + Loop + + If parent.IsKind(VB.SyntaxKind.SetAccessorBlock) Then + Return ReturnStatement() + Else + Return NotImplementedStatement(node) + ' TODO: Implement rewrite of Exit Property statements to return the implicit return variable. + Throw New NotSupportedException("Exit Property in a Property Get block.") + End If + + Case VB.SyntaxKind.ExitFunctionStatement + Return NotImplementedStatement(node) + + ' TODO: Implement rewrite of Exit Function statements to return the implicit return variable. + Throw New NotSupportedException("Exit Function statements.") + + Case VB.SyntaxKind.ExitDoStatement, + VB.SyntaxKind.ExitForStatement, + VB.SyntaxKind.ExitWhileStatement + + ' So long as this exit statement binds to its immediately enclosing block this is simple. + ' Otherwise it would require rewriting with goto statements. + ' TODO: Consider implementing this using binding instead. + Dim parent = node.Parent + Do Until VB.SyntaxFacts.IsDoLoopBlock(parent.Kind) OrElse + (parent.IsKind(VB.SyntaxKind.ForBlock) OrElse parent.IsKind(VB.SyntaxKind.ForEachBlock)) OrElse + parent.IsKind(VB.SyntaxKind.WhileBlock) + + parent = parent.Parent + Loop + + If (node.IsKind(VB.SyntaxKind.ExitDoStatement) AndAlso VB.SyntaxFacts.IsDoLoopBlock(parent.Kind)) OrElse + (node.IsKind(VB.SyntaxKind.ExitForStatement) AndAlso (parent.IsKind(VB.SyntaxKind.ForBlock) OrElse parent.IsKind(VB.SyntaxKind.ForEachBlock))) OrElse + (node.IsKind(VB.SyntaxKind.ExitWhileStatement) AndAlso parent.IsKind(VB.SyntaxKind.WhileBlock)) Then + + Return ContinueStatement() + Else + + Return NotImplementedStatement(node) + + Throw New NotImplementedException("Rewriting Exit statements which branch out of their immediately containing loop block into gotos.") + End If + + Case Else + Throw New NotSupportedException(node.Kind.ToString()) + End Select + + End Function + + Public Overrides Function VisitExternalChecksumDirectiveTrivia(node As VB.Syntax.ExternalChecksumDirectiveTriviaSyntax) As SyntaxNode + + Throw New NotSupportedException(node.Kind.ToString()) + + End Function + + Public Overrides Function VisitExternalSourceDirectiveTrivia(node As VB.Syntax.ExternalSourceDirectiveTriviaSyntax) As SyntaxNode + + Return LineDirectiveTrivia(Literal(node.LineStart.ToString(), CInt(node.LineStart.Value)), isActive:=True) _ + .WithFile(Literal(node.ExternalSource.ToString(), node.ExternalSource.ValueText)) + + End Function + + Public Overrides Function VisitFieldDeclaration(node As VB.Syntax.FieldDeclarationSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Public Overrides Function VisitFinallyBlock(node As VB.Syntax.FinallyBlockSyntax) As SyntaxNode + + If node Is Nothing Then Return Nothing + + Return FinallyClause(Block(List(Visit(node.Statements)))) + + End Function + + Public Overrides Function VisitFinallyStatement(node As VB.Syntax.FinallyStatementSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Public Overrides Function VisitForBlock(node As VB.Syntax.ForBlockSyntax) As SyntaxNode + + Return Visit(node.ForStatement) + + End Function + + Public Overrides Function VisitForEachBlock(node As VB.Syntax.ForEachBlockSyntax) As SyntaxNode + + Return Visit(node.ForEachStatement) + + End Function + + Public Overrides Function VisitForEachStatement(node As VB.Syntax.ForEachStatementSyntax) As SyntaxNode + + Dim forBlock As VB.Syntax.ForEachBlockSyntax = node.Parent + + Dim type As CS.Syntax.TypeSyntax + Dim identifier As SyntaxToken + + Select Case node.ControlVariable.Kind + Case VB.SyntaxKind.IdentifierName + + type = IdentifierName("var") + identifier = VisitIdentifier(CType(node.ControlVariable, VB.Syntax.IdentifierNameSyntax).Identifier) + + Case VB.SyntaxKind.VariableDeclarator + + Dim declarator As VB.Syntax.VariableDeclaratorSyntax = node.ControlVariable + + type = DeriveType(declarator.Names(0), declarator.AsClause, declarator.Initializer) + identifier = VisitIdentifier(declarator.Names(0).Identifier) + + Case Else + + Return NotImplementedStatement(node) + + Throw New NotSupportedException(node.ControlVariable.Kind.ToString()) + End Select + + Return TransferTrivia(forBlock, ForEachStatement( + type, + identifier, + Visit(node.Expression), + Block(List(Visit(forBlock.Statements))) + ) + ) + + End Function + + Public Overrides Function VisitForStatement(node As VB.Syntax.ForStatementSyntax) As SyntaxNode + + Dim forBlock As VB.Syntax.ForBlockSyntax = node.Parent + + Dim type As CS.Syntax.TypeSyntax + Dim identifier As SyntaxToken + + Select Case node.ControlVariable.Kind + Case VB.SyntaxKind.IdentifierName + + ' TODO: Bind to make sure this name isn't referencing an existing variable. + ' If it is then we shouldn't create a var declarator but instead an + ' initialization expression. + type = IdentifierName("var") + identifier = VisitIdentifier(CType(node.ControlVariable, VB.Syntax.IdentifierNameSyntax).Identifier) + + Case VB.SyntaxKind.VariableDeclarator + + Dim declarator As VB.Syntax.VariableDeclaratorSyntax = node.ControlVariable + + type = DeriveType(declarator.Names(0), declarator.AsClause, declarator.Initializer) + identifier = VisitIdentifier(declarator.Names(0).Identifier) + + Case Else + + Return NotImplementedStatement(node) + + Throw New NotSupportedException(node.ControlVariable.Kind.ToString()) + End Select + + Dim toValue = node.ToValue + If toValue.IsKind(VB.SyntaxKind.ParenthesizedExpression) Then + toValue = CType(toValue, VB.Syntax.ParenthesizedExpressionSyntax).Expression + End If + + Dim declarationOpt = VariableDeclaration(type, SingletonSeparatedList(VariableDeclarator(identifier).WithInitializer(EqualsValueClause(Visit(node.FromValue))))) + + Dim conditionOpt As CS.Syntax.ExpressionSyntax = BinaryExpression(CS.SyntaxKind.LessThanOrEqualExpression, IdentifierName(identifier), Visit(toValue)) + + Dim incrementor As CS.Syntax.ExpressionSyntax = PostfixUnaryExpression(CS.SyntaxKind.PostIncrementExpression, IdentifierName(identifier)) + + ' Rewrite ... To Count - 1 to < Count. + If node.StepClause Is Nothing Then + If toValue.IsKind(VB.SyntaxKind.SubtractExpression) Then + Dim subtract As VB.Syntax.BinaryExpressionSyntax = toValue + + If subtract.Right.IsKind(VB.SyntaxKind.NumericLiteralExpression) AndAlso + CInt(CType(subtract.Right, VB.Syntax.LiteralExpressionSyntax).Token.Value) = 1 Then + + conditionOpt = BinaryExpression(CS.SyntaxKind.LessThanExpression, IdentifierName(identifier), Visit(subtract.Left)) + + End If + End If + Else + + Dim stepValue = node.StepClause.StepValue + If stepValue.IsKind(VB.SyntaxKind.ParenthesizedExpression) Then + stepValue = CType(stepValue, VB.Syntax.ParenthesizedExpressionSyntax).Expression + End If + + incrementor = AssignmentExpression(CS.SyntaxKind.AddAssignmentExpression, IdentifierName(identifier), Visit(stepValue)) + + If stepValue.IsKind(VB.SyntaxKind.UnaryMinusExpression) Then + Dim negate As VB.Syntax.UnaryExpressionSyntax = stepValue + + conditionOpt = BinaryExpression(CS.SyntaxKind.GreaterThanOrEqualExpression, IdentifierName(identifier), Visit(toValue)) + + If negate.Operand.IsKind(VB.SyntaxKind.NumericLiteralExpression) AndAlso + CInt(CType(negate.Operand, VB.Syntax.LiteralExpressionSyntax).Token.Value) = 1 Then + + incrementor = PostfixUnaryExpression(CS.SyntaxKind.PostDecrementExpression, IdentifierName(identifier)) + Else + incrementor = AssignmentExpression(CS.SyntaxKind.SubtractAssignmentExpression, IdentifierName(identifier), Visit(negate.Operand)) + End If + End If + End If + + Return TransferTrivia(forBlock, ForStatement(Block(List(Visit(forBlock.Statements)))).WithDeclaration(declarationOpt).WithCondition(conditionOpt).WithIncrementors(SingletonSeparatedList(incrementor))) + + End Function + + Public Overrides Function VisitForStepClause(node As VB.Syntax.ForStepClauseSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Public Overrides Function VisitGenericName(node As VB.Syntax.GenericNameSyntax) As SyntaxNode + + Return GenericName(VisitIdentifier(node.Identifier), VisitTypeArgumentList(node.TypeArgumentList)) + + End Function + + Public Overrides Function VisitGetTypeExpression(node As VB.Syntax.GetTypeExpressionSyntax) As SyntaxNode + + Return TypeOfExpression(Visit(node.Type)) + + End Function + + Public Overrides Function VisitGetXmlNamespaceExpression(node As VB.Syntax.GetXmlNamespaceExpressionSyntax) As SyntaxNode + Return NotImplementedExpression(node) + End Function + + Public Overrides Function VisitGlobalName(node As VB.Syntax.GlobalNameSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Public Overrides Function VisitGoToStatement(node As VB.Syntax.GoToStatementSyntax) As SyntaxNode + + If node.Label.IsKind(VB.SyntaxKind.IdentifierLabel) Then + Return TransferTrivia(node, GotoStatement(CS.SyntaxKind.GotoStatement, IdentifierName(VisitIdentifier(node.Label.LabelToken)))) + Else + Return NotImplementedStatement(node) + ' Rewrite this label with an alpha prefix. + Throw New NotSupportedException("Goto statements with numeric label names.") + End If + + End Function + + Public Overrides Function VisitGroupAggregation(node As VB.Syntax.GroupAggregationSyntax) As SyntaxNode + Return MyBase.VisitGroupAggregation(node) + End Function + + Public Overrides Function VisitGroupByClause(node As VB.Syntax.GroupByClauseSyntax) As SyntaxNode + Return MyBase.VisitGroupByClause(node) + End Function + + Public Overrides Function VisitGroupJoinClause(node As VB.Syntax.GroupJoinClauseSyntax) As SyntaxNode + Return MyBase.VisitGroupJoinClause(node) + End Function + + Public Overrides Function VisitHandlesClause(node As VB.Syntax.HandlesClauseSyntax) As SyntaxNode + Return MyBase.VisitHandlesClause(node) + End Function + + Public Overrides Function VisitHandlesClauseItem(node As VB.Syntax.HandlesClauseItemSyntax) As SyntaxNode + Return MyBase.VisitHandlesClauseItem(node) + End Function + + Public Overrides Function VisitIdentifierName(node As VB.Syntax.IdentifierNameSyntax) As SyntaxNode + + Return IdentifierName(VisitIdentifier(node.Identifier)) + + End Function + + Protected Function VisitIdentifier(token As SyntaxToken) As SyntaxToken + + If token.IsKind(VB.SyntaxKind.None) Then Return Nothing + + Dim text = token.ValueText + + ' Strip out type characters. + If Not Char.IsLetterOrDigit(text(text.Length - 1)) OrElse text.EndsWith("_") Then + text = text.Substring(0, text.Length - 1) + End If + + If text = "_" Then + Return Identifier("_" & text) + Else + Return Identifier(text) + End If + + End Function + + Public Overrides Function VisitIfDirectiveTrivia(node As VB.Syntax.IfDirectiveTriviaSyntax) As SyntaxNode + + Return IfDirectiveTrivia(Visit(node.Condition), isActive:=False, branchTaken:=False, conditionValue:=False) + + End Function + + Public Overrides Function VisitIfStatement(node As VB.Syntax.IfStatementSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Public Overrides Function VisitImplementsClause(node As VB.Syntax.ImplementsClauseSyntax) As SyntaxNode + Return MyBase.VisitImplementsClause(node) + End Function + + Public Overrides Function VisitImplementsStatement(node As VB.Syntax.ImplementsStatementSyntax) As SyntaxNode + + If node.Types.Count = 1 Then + Return Visit(node.Types(0)) + Else + Throw New InvalidOperationException() + End If + + End Function + + Protected Function VisitImplementsStatements(statements As IEnumerable(Of VB.Syntax.ImplementsStatementSyntax)) As IEnumerable(Of SyntaxNode) + + Return Visit((Aggregate statement In statements Into SelectMany(statement.Types))) + + End Function + + Public Overrides Function VisitImportsStatement(node As VB.Syntax.ImportsStatementSyntax) As SyntaxNode + + If node.ImportsClauses.Count > 1 Then + Throw New InvalidOperationException() + End If + + Return Visit(node.ImportsClauses(0)) + + End Function + + Protected Function VisitImportsStatements(statements As IEnumerable(Of VB.Syntax.ImportsStatementSyntax)) As IEnumerable(Of SyntaxNode) + + Return Visit((Aggregate statement In statements Into SelectMany(statement.ImportsClauses))) + + End Function + + Public Overrides Function VisitInferredFieldInitializer(node As VB.Syntax.InferredFieldInitializerSyntax) As SyntaxNode + + Return Visit(node.Expression) + + End Function + + Public Overrides Function VisitInheritsStatement(node As VB.Syntax.InheritsStatementSyntax) As SyntaxNode + + If node.Types.Count = 1 Then + Return Visit(node.Types(0)) + Else + Throw New InvalidOperationException() + End If + + End Function + + Protected Function VisitInheritsStatements(statements As IEnumerable(Of VB.Syntax.InheritsStatementSyntax)) As IEnumerable(Of SyntaxNode) + + Return Visit((Aggregate statement In statements Into SelectMany(statement.Types))) + + End Function + + Protected Overridable Function VisitInstanceExpression(node As VB.Syntax.InstanceExpressionSyntax) As SyntaxNode + + Select Case node.Kind + Case VB.SyntaxKind.MeExpression + Return ThisExpression() + Case VB.SyntaxKind.MyBaseExpression + Return BaseExpression() + Case VB.SyntaxKind.MyClassExpression + Return NotImplementedExpression(node) + Throw New NotSupportedException("C# doesn't have a MyClass equivalent") + Case Else + Throw New NotSupportedException(node.Kind.ToString()) + End Select + + End Function + + Public Overrides Function VisitMeExpression(node As VB.Syntax.MeExpressionSyntax) As SyntaxNode + Return VisitInstanceExpression(node) + End Function + + Public Overrides Function VisitMyBaseExpression(node As VB.Syntax.MyBaseExpressionSyntax) As SyntaxNode + Return VisitInstanceExpression(node) + End Function + + Public Overrides Function VisitMyClassExpression(node As VB.Syntax.MyClassExpressionSyntax) As SyntaxNode + Return VisitInstanceExpression(node) + End Function + + Public Overrides Function VisitInvocationExpression(node As VB.Syntax.InvocationExpressionSyntax) As SyntaxNode + + ' TODO: Use binding to detect whether this is an invocation or an index, + ' and if an index whether off a property or the result of an implicit method invocation. + Return InvocationExpression(Visit(node.Expression), VisitArgumentList(node.ArgumentList)) + + End Function + + Public Overrides Function VisitJoinCondition(node As VB.Syntax.JoinConditionSyntax) As SyntaxNode + Return MyBase.VisitJoinCondition(node) + End Function + + Public Overrides Function VisitSimpleJoinClause(node As VB.Syntax.SimpleJoinClauseSyntax) As SyntaxNode + Return MyBase.VisitSimpleJoinClause(node) + End Function + + Public Overrides Function VisitLabelStatement(node As VB.Syntax.LabelStatementSyntax) As SyntaxNode + + Return LabeledStatement(VisitIdentifier(node.LabelToken), EmptyStatement()) + + End Function + + Public Overrides Function VisitLambdaHeader(node As VB.Syntax.LambdaHeaderSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Public Overrides Function VisitLiteralExpression(node As VB.Syntax.LiteralExpressionSyntax) As SyntaxNode + + Select Case node.Kind + Case VB.SyntaxKind.StringLiteralExpression + + ' VB String literals are effectively implicitly escaped (a.k.a verbatim). + Dim valueText = If(node.Token.ValueText.Contains("\"), + "@" & """" & node.Token.ValueText & """", + """" & node.Token.ValueText & """" + ) + + Return LiteralExpression(CS.SyntaxKind.StringLiteralExpression, Literal(valueText, CStr(node.Token.Value))) + + Case VB.SyntaxKind.CharacterLiteralExpression + + Return LiteralExpression(CS.SyntaxKind.StringLiteralExpression, Literal("'" & node.Token.ValueText & "'", CChar(node.Token.Value))) + + Case VB.SyntaxKind.TrueLiteralExpression + + Return LiteralExpression(CS.SyntaxKind.TrueLiteralExpression) + + Case VB.SyntaxKind.FalseLiteralExpression + + Return LiteralExpression(CS.SyntaxKind.FalseLiteralExpression) + + Case VB.SyntaxKind.DateLiteralExpression + + Return NotImplementedExpression(node) + + ' TODO: Rewrite to new global::System.DateTime.Parse("yyyy-MM-dd HH:mm:ss") + Throw New NotImplementedException(node.ToString()) + + Case VB.SyntaxKind.NumericLiteralExpression + + Select Case node.Token.Kind + + Case VB.SyntaxKind.DecimalLiteralToken + + Return LiteralExpression(CS.SyntaxKind.NumericLiteralExpression, Literal(node.Token.ValueText, CDec(node.Token.Value))) + + Case VB.SyntaxKind.FloatingLiteralToken + + Return LiteralExpression(CS.SyntaxKind.NumericLiteralExpression, Literal(node.Token.ValueText, CDbl(node.Token.Value))) + + Case VB.SyntaxKind.IntegerLiteralToken + + Dim literalText As String = Nothing + + Select Case node.Token.GetBase() + Case VB.Syntax.LiteralBase.Decimal + literalText = node.Token.ValueText + + Case VB.Syntax.LiteralBase.Hexadecimal, + VB.Syntax.LiteralBase.Octal + + literalText = "0x" & CType(node.Token.Value, IFormattable).ToString("X02", formatProvider:=Nothing) + + End Select + + Dim literalToken As SyntaxToken + + Select Case node.Token.GetTypeCharacter() + Case VB.Syntax.TypeCharacter.ShortLiteral + + literalToken = Literal(literalText, CShort(node.Token.Value)) + + Case VB.Syntax.TypeCharacter.IntegerLiteral + + literalToken = Literal(literalText, CInt(node.Token.Value)) + + Case VB.Syntax.TypeCharacter.LongLiteral + + literalToken = Literal(literalText, CLng(node.Token.Value)) + + Case VB.Syntax.TypeCharacter.UShortLiteral + + literalToken = Literal(literalText, CUShort(node.Token.Value)) + + Case VB.Syntax.TypeCharacter.UIntegerLiteral + + literalToken = Literal(literalText, CUInt(node.Token.Value)) + + Case VB.Syntax.TypeCharacter.ULongLiteral + + literalToken = Literal(literalText, CULng(node.Token.Value)) + + Case Else ' Default to Integer type + + literalToken = Literal(literalText, CInt(node.Token.Value)) + + End Select + + Return LiteralExpression(CS.SyntaxKind.NumericLiteralExpression, literalToken) + + Case Else + Return NotImplementedExpression(node) + + Throw New NotSupportedException(node.Token.Kind.ToString()) + End Select + + Case VB.SyntaxKind.NothingLiteralExpression + ' TODO: Bind this expression in context to determine whether this translates to null or default(T). + Return LiteralExpression(CS.SyntaxKind.NullLiteralExpression) + + Case Else + Return NotImplementedExpression(node) + + Throw New NotSupportedException(node.Kind.ToString()) + End Select + + End Function + + Public Overrides Function VisitLocalDeclarationStatement(node As VB.Syntax.LocalDeclarationStatementSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Public Overrides Function VisitLoopStatement(node As VB.Syntax.LoopStatementSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Public Overrides Function VisitMemberAccessExpression(node As VB.Syntax.MemberAccessExpressionSyntax) As SyntaxNode + + If node.Expression Is Nothing Then + + Return NotImplementedExpression(node) + ' TODO: Rewrite WithBlock member access. + Throw New NotImplementedException(node.ToString()) + End If + + Select Case node.Kind + + Case VB.SyntaxKind.SimpleMemberAccessExpression + + Return MemberAccessExpression(CS.SyntaxKind.SimpleMemberAccessExpression, Visit(node.Expression), Visit(node.Name)) + + Case VB.SyntaxKind.DictionaryAccessExpression + + Return NotImplementedExpression(node) + ' TODO: Rewrite to Invocation. + Throw New NotImplementedException(node.ToString()) + + Case Else + Throw New NotSupportedException(node.Kind.ToString()) + End Select + + End Function + + Public Overrides Function VisitSimpleImportsClause(node As VB.Syntax.SimpleImportsClauseSyntax) As SyntaxNode + + If node.Alias Is Nothing Then + Return TransferTrivia(node.Parent, UsingDirective(Visit(node.Name))) + Else + Return TransferTrivia(node.Parent, UsingDirective(Visit(node.Name)).WithAlias(NameEquals(CS.SyntaxFactory.IdentifierName(VisitIdentifier(node.Alias.Identifier))))) + End If + + End Function + + Protected Function VisitMembers(statements As IEnumerable(Of VB.Syntax.StatementSyntax)) As IEnumerable(Of CS.Syntax.MemberDeclarationSyntax) + Dim members As New List(Of CS.Syntax.MemberDeclarationSyntax) + + For Each statement In statements + Dim converted = Visit(statement) + + If TypeOf converted Is CS.Syntax.MemberDeclarationSyntax Then + members.Add(converted) + ElseIf TypeOf converted Is CS.Syntax.StatementSyntax Then + members.Add(GlobalStatement(converted)) + Else + Throw New NotSupportedException(converted.Kind.ToString()) + End If + Next + + Return members + End Function + + Public Overrides Function VisitMethodBlock(node As VB.Syntax.MethodBlockSyntax) As SyntaxNode + Return Visit(node.SubOrFunctionStatement) + End Function + + Public Overrides Function VisitConstructorBlock(node As ConstructorBlockSyntax) As SyntaxNode + Return Visit(node.SubNewStatement) + End Function + + Public Overrides Function VisitOperatorBlock(node As OperatorBlockSyntax) As SyntaxNode + Return Visit(node.OperatorStatement) + End Function + + Public Overrides Function VisitAccessorBlock(node As AccessorBlockSyntax) As SyntaxNode + Return Visit(node.AccessorStatement) + End Function + + Public Overrides Function VisitMethodStatement(node As VB.Syntax.MethodStatementSyntax) As SyntaxNode + + ' A MustInherit method, or a method inside an Interface definition will be directly parented by the TypeBlock. + Dim methodBlock = TryCast(node.Parent, VB.Syntax.MethodBlockSyntax) + + Dim triviaSource As SyntaxNode = node + If methodBlock IsNot Nothing Then + triviaSource = methodBlock + End If + + Return TransferTrivia(triviaSource, MethodDeclaration( + DeriveType(node.Identifier, node.AsClause, node.SubOrFunctionKeyword), + VisitIdentifier(node.Identifier)) _ + .WithAttributeLists(List(VisitAttributeLists(node.AttributeLists))) _ + .WithModifiers(TokenList(VisitModifiers(node.Modifiers))) _ + .WithTypeParameterList(VisitTypeParameterList(node.TypeParameterList)) _ + .WithParameterList(VisitParameterList(node.ParameterList)) _ + .WithConstraintClauses(List(VisitTypeParameterConstraintClauses(node.TypeParameterList))) _ + .WithBody(If(methodBlock Is Nothing, Nothing, Block(List(Visit(methodBlock.Statements))))) _ + .WithSemicolonToken(If(methodBlock Is Nothing, Token(CS.SyntaxKind.SemicolonToken), Nothing)) + ) + + End Function + + Protected Function VisitModifier(token As SyntaxToken) As SyntaxToken + + Dim kind As CS.SyntaxKind + + Select Case token.Kind + Case VB.SyntaxKind.PublicKeyword + kind = CS.SyntaxKind.PublicKeyword + Case VB.SyntaxKind.PrivateKeyword + kind = CS.SyntaxKind.PrivateKeyword + Case VB.SyntaxKind.ProtectedKeyword + kind = CS.SyntaxKind.ProtectedKeyword + Case VB.SyntaxKind.FriendKeyword + kind = CS.SyntaxKind.InternalKeyword + Case VB.SyntaxKind.SharedKeyword + kind = CS.SyntaxKind.StaticKeyword + Case VB.SyntaxKind.OverridesKeyword + kind = CS.SyntaxKind.OverrideKeyword + Case VB.SyntaxKind.OverridableKeyword + kind = CS.SyntaxKind.VirtualKeyword + Case VB.SyntaxKind.MustOverrideKeyword + kind = CS.SyntaxKind.AbstractKeyword + Case VB.SyntaxKind.NotOverridableKeyword + kind = CS.SyntaxKind.SealedKeyword + Case VB.SyntaxKind.OverloadsKeyword + kind = CS.SyntaxKind.NewKeyword + Case VB.SyntaxKind.MustInheritKeyword + kind = CS.SyntaxKind.AbstractKeyword + Case VB.SyntaxKind.NotInheritableKeyword + kind = CS.SyntaxKind.SealedKeyword + Case VB.SyntaxKind.PartialKeyword + kind = CS.SyntaxKind.PartialKeyword + Case VB.SyntaxKind.ByRefKeyword + kind = CS.SyntaxKind.RefKeyword + Case VB.SyntaxKind.ParamArrayKeyword + kind = CS.SyntaxKind.ParamsKeyword + Case VB.SyntaxKind.NarrowingKeyword + kind = CS.SyntaxKind.ExplicitKeyword + Case VB.SyntaxKind.WideningKeyword + kind = CS.SyntaxKind.ImplicitKeyword + Case VB.SyntaxKind.ConstKeyword + kind = CS.SyntaxKind.ConstKeyword + Case VB.SyntaxKind.ReadOnlyKeyword + + If TypeOf token.Parent Is VB.Syntax.PropertyStatementSyntax Then + kind = CS.SyntaxKind.None + Else + kind = CS.SyntaxKind.ReadOnlyKeyword + End If + + Case VB.SyntaxKind.DimKeyword + kind = CS.SyntaxKind.None + + Case VB.SyntaxKind.AsyncKeyword + kind = CS.SyntaxKind.AsyncKeyword + Case Else + Return NotImplementedModifier(token) + + Throw New NotSupportedException(token.Kind.ToString()) + End Select + + Return CS.SyntaxFactory.Token(kind) + + End Function + + Protected Function VisitModifiers(tokens As IEnumerable(Of SyntaxToken)) As IEnumerable(Of SyntaxToken) + + Return From + token In tokens + Where + Not token.IsKind(VB.SyntaxKind.ByValKeyword) AndAlso + Not token.IsKind(VB.SyntaxKind.OptionalKeyword) + Select + translation = VisitModifier(token) + Where + Not translation.IsKind(CS.SyntaxKind.None) + + End Function + + Public Overrides Function VisitModifiedIdentifier(node As VB.Syntax.ModifiedIdentifierSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Public Overrides Function VisitMultiLineIfBlock(node As VB.Syntax.MultiLineIfBlockSyntax) As SyntaxNode + + Dim elseOpt As CS.Syntax.ElseClauseSyntax = Nothing + + ' TODO: Transfer trivia for each elseif/else block. + If node.ElseBlock IsNot Nothing Then + elseOpt = ElseClause(Block(List(Visit(node.ElseBlock.Statements)))) + End If + + For i = node.ElseIfBlocks.Count - 1 To 0 Step -1 + elseOpt = ElseClause( + IfStatement( + Visit(node.ElseIfBlocks(i).ElseIfStatement.Condition), + Block(List(Visit(node.ElseIfBlocks(i).Statements)))) _ + .WithElse(elseOpt) + ) + Next + + Return TransferTrivia(node, IfStatement( + Visit(node.IfStatement.Condition), + Block(List(Visit(node.Statements)))) _ + .WithElse(elseOpt) + ) + + End Function + + Public Overrides Function VisitMultiLineLambdaExpression(node As VB.Syntax.MultiLineLambdaExpressionSyntax) As SyntaxNode + + Dim asyncKeyword = If(node.SubOrFunctionHeader.Modifiers.Any(VB.SyntaxKind.AsyncKeyword), + Token(CS.SyntaxKind.AsyncKeyword), + Nothing) + + Dim parameterList = VisitParameterList(node.SubOrFunctionHeader.ParameterList) + + Dim arrowToken = Token(CS.SyntaxKind.EqualsGreaterThanToken) + + Dim body = Block(List(Visit(node.Statements))) + + Return ParenthesizedLambdaExpression(asyncKeyword, parameterList, arrowToken, body) + + End Function + + Public Overrides Function VisitNamedFieldInitializer(node As VB.Syntax.NamedFieldInitializerSyntax) As SyntaxNode + + Return If(node.Parent.Parent.IsKind(VB.SyntaxKind.AnonymousObjectCreationExpression), + CType(AnonymousObjectMemberDeclarator(NameEquals(VisitIdentifierName(node.Name)), Visit(node.Expression)), SyntaxNode), + AssignmentExpression(CS.SyntaxKind.SimpleAssignmentExpression, VisitIdentifierName(node.Name), Visit(node.Expression))) + + End Function + + Public Overrides Function VisitNamespaceBlock(node As VB.Syntax.NamespaceBlockSyntax) As SyntaxNode + + Return VisitNamespaceStatement(node.NamespaceStatement) + + End Function + + Public Overrides Function VisitNamespaceStatement(node As VB.Syntax.NamespaceStatementSyntax) As SyntaxNode + + Dim namespaceBlock As VB.Syntax.NamespaceBlockSyntax = node.Parent + + If node.Name.IsKind(VB.SyntaxKind.GlobalName) Then + + ' TODO: Split all members to declare in global namespace. + Throw New NotImplementedException(node.ToString()) + + Else + Dim baseName = node.Name + Do While TypeOf baseName Is VB.Syntax.QualifiedNameSyntax + baseName = CType(baseName, VB.Syntax.QualifiedNameSyntax).Left + Loop + + Dim remainingNames = TryCast(baseName.Parent, VB.Syntax.QualifiedNameSyntax) + + Dim finalName As CS.Syntax.NameSyntax + + ' Strip out the Global name. + If baseName.IsKind(VB.SyntaxKind.GlobalName) Then + finalName = Visit(remainingNames.Right) + remainingNames = TryCast(remainingNames.Parent, VB.Syntax.QualifiedNameSyntax) + ElseIf RootNamespaceName IsNot Nothing Then + finalName = QualifiedName(RootNamespaceName, Visit(baseName)) + Else + finalName = Visit(baseName) + End If + + Do Until remainingNames Is Nothing + finalName = QualifiedName(finalName, Visit(remainingNames.Right)) + remainingNames = TryCast(remainingNames.Parent, VB.Syntax.QualifiedNameSyntax) + Loop + + Return TransferTrivia(node, NamespaceDeclaration(finalName).WithMembers(List(Visit(namespaceBlock.Members)))) + + End If + + End Function + + Public Overrides Function VisitNextStatement(node As VB.Syntax.NextStatementSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Public Overrides Function VisitNullableType(node As VB.Syntax.NullableTypeSyntax) As SyntaxNode + + Return NullableType(Visit(node.ElementType)) + + End Function + + Public Overrides Function VisitObjectCollectionInitializer(node As VB.Syntax.ObjectCollectionInitializerSyntax) As SyntaxNode + + ' TODO: Figure out what to do if the initializers contain nested initializers that invoke extension methods. + Return VisitCollectionInitializer(node.Initializer) + + End Function + + Public Overrides Function VisitObjectCreationExpression(node As VB.Syntax.ObjectCreationExpressionSyntax) As SyntaxNode + + Return ObjectCreationExpression(Visit(node.Type)) _ + .WithArgumentList(VisitArgumentList(node.ArgumentList)) _ + .WithInitializer(Visit(node.Initializer)) + + End Function + + Public Overrides Function VisitObjectMemberInitializer(node As VB.Syntax.ObjectMemberInitializerSyntax) As SyntaxNode + + Return InitializerExpression(CS.SyntaxKind.ObjectInitializerExpression, SeparatedList(Visit(node.Initializers).Cast(Of CS.Syntax.ExpressionSyntax))) + + End Function + + Public Overrides Function VisitOmittedArgument(node As VB.Syntax.OmittedArgumentSyntax) As SyntaxNode + + Return CS.SyntaxFactory.Argument(NotImplementedExpression(node)) + + ' TODO: Bind to discover default values. + Throw New NotImplementedException(node.ToString()) + End Function + + Public Overrides Function VisitOnErrorGoToStatement(node As VB.Syntax.OnErrorGoToStatementSyntax) As SyntaxNode + + Return NotImplementedStatement(node) + + End Function + + Public Overrides Function VisitOnErrorResumeNextStatement(node As VB.Syntax.OnErrorResumeNextStatementSyntax) As SyntaxNode + + Return NotImplementedStatement(node) + + End Function + + Public Overrides Function VisitOperatorStatement(node As VB.Syntax.OperatorStatementSyntax) As SyntaxNode + + Dim operatorBlock As VB.Syntax.OperatorBlockSyntax = node.Parent + + Dim kind As CS.SyntaxKind + Select Case node.OperatorToken.Kind + + Case VB.SyntaxKind.CTypeKeyword + + Dim otherModifiers As New List(Of SyntaxToken)(node.Modifiers.Count) + Dim implicitOrExplicitKeyword As SyntaxToken + + For Each modifier In node.Modifiers + Select Case modifier.Kind + Case VB.SyntaxKind.NarrowingKeyword + implicitOrExplicitKeyword = Token(CS.SyntaxKind.ExplicitKeyword) + Case VB.SyntaxKind.WideningKeyword + implicitOrExplicitKeyword = Token(CS.SyntaxKind.ImplicitKeyword) + Case Else + otherModifiers.Add(modifier) + End Select + Next + + Return TransferTrivia(operatorBlock, ConversionOperatorDeclaration( + implicitOrExplicitKeyword, + VisitSimpleAsClause(node.AsClause)) _ + .WithAttributeLists(List(VisitAttributeLists(node.AttributeLists))) _ + .WithModifiers(TokenList(VisitModifiers(otherModifiers))) _ + .WithParameterList(VisitParameterList(node.ParameterList)) _ + .WithBody(Block(List(Visit(operatorBlock.Statements)))) + ) + + Case VB.SyntaxKind.IsTrueKeyword + kind = CS.SyntaxKind.TrueKeyword + Case VB.SyntaxKind.IsFalseKeyword + kind = CS.SyntaxKind.FalseKeyword + Case VB.SyntaxKind.NotKeyword + kind = CS.SyntaxKind.BitwiseNotExpression + Case VB.SyntaxKind.PlusToken + kind = CS.SyntaxKind.PlusToken + Case VB.SyntaxKind.MinusToken + kind = CS.SyntaxKind.MinusMinusToken + Case VB.SyntaxKind.AsteriskToken + kind = CS.SyntaxKind.AsteriskToken + Case VB.SyntaxKind.SlashToken + kind = CS.SyntaxKind.SlashToken + Case VB.SyntaxKind.LessThanLessThanToken + kind = CS.SyntaxKind.LessThanLessThanToken + Case VB.SyntaxKind.GreaterThanGreaterThanToken + kind = CS.SyntaxKind.GreaterThanGreaterThanToken + Case VB.SyntaxKind.ModKeyword + kind = CS.SyntaxKind.PercentToken + Case VB.SyntaxKind.OrKeyword + kind = CS.SyntaxKind.BarToken + Case VB.SyntaxKind.XorKeyword + kind = CS.SyntaxKind.CaretToken + Case VB.SyntaxKind.AndKeyword + kind = CS.SyntaxKind.AmpersandToken + Case VB.SyntaxKind.EqualsToken + kind = CS.SyntaxKind.EqualsEqualsToken + Case VB.SyntaxKind.LessThanGreaterThanToken + kind = CS.SyntaxKind.ExclamationEqualsToken + Case VB.SyntaxKind.LessThanToken + kind = CS.SyntaxKind.LessThanToken + Case VB.SyntaxKind.LessThanEqualsToken + kind = CS.SyntaxKind.LessThanEqualsToken + Case VB.SyntaxKind.GreaterThanEqualsToken + kind = CS.SyntaxKind.GreaterThanEqualsToken + Case VB.SyntaxKind.GreaterThanToken + kind = CS.SyntaxKind.GreaterThanToken + + Case VB.SyntaxKind.AmpersandToken, + VB.SyntaxKind.BackslashToken, + VB.SyntaxKind.LikeKeyword, + VB.SyntaxKind.CaretToken + + Return NotImplementedMember(node) + ' TODO: Rewrite this as a normal method with the System.Runtime.CompilerServices.SpecialName attribute. + Throw New NotImplementedException(node.ToString()) + + End Select + + Return TransferTrivia(operatorBlock, OperatorDeclaration( + DeriveType(node.OperatorToken, node.AsClause, node.OperatorKeyword), + Token(kind)) _ + .WithAttributeLists(List(VisitAttributeLists(node.AttributeLists))) _ + .WithModifiers(TokenList(VisitModifiers(node.Modifiers))) _ + .WithParameterList(VisitParameterList(node.ParameterList)) _ + .WithBody(Block(List(Visit(operatorBlock.Statements)))) + ) + + End Function + + Public Overrides Function VisitOptionStatement(node As VB.Syntax.OptionStatementSyntax) As SyntaxNode + + Select Case node.NameKeyword.Kind + Case VB.SyntaxKind.ExplicitKeyword + If node.ValueKeyword.IsKind(VB.SyntaxKind.OffKeyword) Then + IsOptionExplicitOn = False + + ' TODO: Log this. + ''Throw New NotSupportedException("Option Explicit Off") + End If + Case VB.SyntaxKind.CompareKeyword + If node.ValueKeyword.IsKind(VB.SyntaxKind.TextKeyword) Then + IsOptionCompareBinary = False + + ' TODO: Log this. + ''Throw New NotImplementedException("Option Compare Text") + End If + Case VB.SyntaxKind.StrictKeyword + + IsOptionStrictOn = Not node.ValueKeyword.IsKind(VB.SyntaxKind.OffKeyword) + + Case VB.SyntaxKind.InferKeyword + + IsOptionInferOn = Not node.ValueKeyword.IsKind(VB.SyntaxKind.OffKeyword) + + End Select + + Return Nothing + End Function + + Public Overrides Function VisitOrderByClause(node As VB.Syntax.OrderByClauseSyntax) As SyntaxNode + Return MyBase.VisitOrderByClause(node) + End Function + + Public Overrides Function VisitOrdering(node As VB.Syntax.OrderingSyntax) As SyntaxNode + Return MyBase.VisitOrdering(node) + End Function + + Public Overrides Function VisitParameter(node As VB.Syntax.ParameterSyntax) As SyntaxNode + + Return Parameter( + List(VisitAttributeLists(node.AttributeLists)), + TokenList(VisitModifiers(node.Modifiers)), + DeriveType(node.Identifier, node.AsClause, initializer:=Nothing), + VisitIdentifier(node.Identifier.Identifier), + VisitEqualsValue(node.Default) + ) + + End Function + + Public Overrides Function VisitParameterList(node As VB.Syntax.ParameterListSyntax) As SyntaxNode + + If node Is Nothing Then Return ParameterList() + + Return ParameterList(SeparatedList(Visit(node.Parameters).Cast(Of CS.Syntax.ParameterSyntax))) + + End Function + + Public Overrides Function VisitParenthesizedExpression(node As VB.Syntax.ParenthesizedExpressionSyntax) As SyntaxNode + + Return ParenthesizedExpression(Visit(node.Expression)) + + End Function + + Public Overrides Function VisitPartitionClause(node As VB.Syntax.PartitionClauseSyntax) As SyntaxNode + Return MyBase.VisitPartitionClause(node) + End Function + + Public Overrides Function VisitPartitionWhileClause(node As VB.Syntax.PartitionWhileClauseSyntax) As SyntaxNode + Return MyBase.VisitPartitionWhileClause(node) + End Function + + Public Overrides Function VisitPredefinedCastExpression(node As VB.Syntax.PredefinedCastExpressionSyntax) As SyntaxNode + + ' NOTE: For conversions between intrinsic types this is an over-simplification. + ' Depending on the source and target types this may be a C# cast, a VB runtime call, a BCL call, or a simple IL instruction. + Dim kind As CS.SyntaxKind + + Select Case node.Keyword.Kind + Case VB.SyntaxKind.CByteKeyword + kind = CS.SyntaxKind.ByteKeyword + Case VB.SyntaxKind.CUShortKeyword + kind = CS.SyntaxKind.UShortKeyword + Case VB.SyntaxKind.CUIntKeyword + kind = CS.SyntaxKind.UIntKeyword + Case VB.SyntaxKind.CULngKeyword + kind = CS.SyntaxKind.ULongKeyword + Case VB.SyntaxKind.CSByteKeyword + kind = CS.SyntaxKind.SByteKeyword + Case VB.SyntaxKind.CShortKeyword + kind = CS.SyntaxKind.ShortKeyword + Case VB.SyntaxKind.CIntKeyword + kind = CS.SyntaxKind.IntKeyword + Case VB.SyntaxKind.CLngKeyword + kind = CS.SyntaxKind.LongKeyword + Case VB.SyntaxKind.CSngKeyword + kind = CS.SyntaxKind.FloatKeyword + Case VB.SyntaxKind.CDblKeyword + kind = CS.SyntaxKind.DoubleKeyword + Case VB.SyntaxKind.CDecKeyword + kind = CS.SyntaxKind.DecimalKeyword + Case VB.SyntaxKind.CStrKeyword + kind = CS.SyntaxKind.StringKeyword + Case VB.SyntaxKind.CCharKeyword + kind = CS.SyntaxKind.CharKeyword + Case VB.SyntaxKind.CDateKeyword + Return ParenthesizedExpression(CastExpression(ParseTypeName("global::System.DateTime"), Visit(node.Expression))) + Case VB.SyntaxKind.CBoolKeyword + kind = CS.SyntaxKind.BoolKeyword + Case VB.SyntaxKind.CObjKeyword + kind = CS.SyntaxKind.ObjectKeyword + Case Else + Throw New NotSupportedException(node.Keyword.Kind.ToString()) + End Select + + Return ParenthesizedExpression(CastExpression(PredefinedType(Token(kind)), Visit(node.Expression))) + + End Function + + Public Overrides Function VisitPredefinedType(node As VB.Syntax.PredefinedTypeSyntax) As SyntaxNode + + Dim kind As CS.SyntaxKind + + Select Case node.Keyword.Kind + Case VB.SyntaxKind.ByteKeyword + kind = CS.SyntaxKind.ByteKeyword + Case VB.SyntaxKind.UShortKeyword + kind = CS.SyntaxKind.UShortKeyword + Case VB.SyntaxKind.UIntegerKeyword + kind = CS.SyntaxKind.UIntKeyword + Case VB.SyntaxKind.ULongKeyword + kind = CS.SyntaxKind.ULongKeyword + Case VB.SyntaxKind.SByteKeyword + kind = CS.SyntaxKind.SByteKeyword + Case VB.SyntaxKind.ShortKeyword + kind = CS.SyntaxKind.ShortKeyword + Case VB.SyntaxKind.IntegerKeyword + kind = CS.SyntaxKind.IntKeyword + Case VB.SyntaxKind.LongKeyword + kind = CS.SyntaxKind.LongKeyword + Case VB.SyntaxKind.SingleKeyword + kind = CS.SyntaxKind.FloatKeyword + Case VB.SyntaxKind.DoubleKeyword + kind = CS.SyntaxKind.DoubleKeyword + Case VB.SyntaxKind.DecimalKeyword + kind = CS.SyntaxKind.DecimalKeyword + Case VB.SyntaxKind.StringKeyword + kind = CS.SyntaxKind.StringKeyword + Case VB.SyntaxKind.CharKeyword + kind = CS.SyntaxKind.CharKeyword + Case VB.SyntaxKind.DateKeyword + Return ParseTypeName("global::System.DateTime") + Case VB.SyntaxKind.BooleanKeyword + kind = CS.SyntaxKind.BoolKeyword + Case VB.SyntaxKind.ObjectKeyword + kind = CS.SyntaxKind.ObjectKeyword + Case Else + Throw New NotSupportedException(node.Keyword.Kind.ToString()) + End Select + + Return PredefinedType(Token(kind)) + + End Function + + Public Overrides Function VisitPropertyBlock(node As VB.Syntax.PropertyBlockSyntax) As SyntaxNode + + Return VisitPropertyStatement(node.PropertyStatement) + + End Function + + Public Overrides Function VisitPropertyStatement(node As VB.Syntax.PropertyStatementSyntax) As SyntaxNode + + Dim propertyBlockOpt = TryCast(node.Parent, VB.Syntax.PropertyBlockSyntax) + + If propertyBlockOpt IsNot Nothing Then + Return TransferTrivia(propertyBlockOpt, PropertyDeclaration( + DeriveType(node.Identifier, node.AsClause), + VisitIdentifier(node.Identifier)) _ + .WithAttributeLists(List(VisitAttributeLists(node.AttributeLists))) _ + .WithModifiers(TokenList(VisitModifiers(node.Modifiers))) _ + .WithAccessorList(AccessorList(List(Visit(propertyBlockOpt.Accessors)))) + ) + Else + + Dim accessors = New List(Of CS.Syntax.AccessorDeclarationSyntax)() From { + AccessorDeclaration(CS.SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(SemicolonToken), + AccessorDeclaration(CS.SyntaxKind.SetAccessorDeclaration).WithSemicolonToken(SemicolonToken) + } + + ' For MustOverride properties and properties in interfaces we have to check the modifiers + ' to determine whether get or set accessors should be generated. + For Each modifier In node.Modifiers + Select Case modifier.Kind + Case VB.SyntaxKind.ReadOnlyKeyword + accessors.RemoveAt(1) + Case (VB.SyntaxKind.WriteOnlyKeyword) + accessors.RemoveAt(0) + End Select + Next + + ' TODO: Transfer initializers on the auto-prop to the constructor. + Return TransferTrivia(node, PropertyDeclaration( + DeriveType(node.Identifier, node.AsClause), + VisitIdentifier(node.Identifier)) _ + .WithAttributeLists(List(VisitAttributeLists(node.AttributeLists))) _ + .WithModifiers(TokenList(VisitModifiers(node.Modifiers))) _ + .WithAccessorList(AccessorList(List(accessors))) + ) + End If + + End Function + + Public Overrides Function VisitQualifiedName(node As VB.Syntax.QualifiedNameSyntax) As SyntaxNode + + If TypeOf node.Left Is VB.Syntax.GlobalNameSyntax Then + Return AliasQualifiedName(IdentifierName("global"), Visit(node.Right)) + Else + Return QualifiedName(Visit(node.Left), Visit(node.Right)) + End If + + End Function + + Public Overrides Function VisitQueryExpression(node As VB.Syntax.QueryExpressionSyntax) As SyntaxNode + + Return (New QueryClauseConvertingVisitor(parent:=Me)).Visit(node) + + End Function + + Public Overrides Function VisitRaiseEventStatement(node As VB.Syntax.RaiseEventStatementSyntax) As SyntaxNode + + ' TODO: Rewrite to a conditional invocation based on a thread-safe null check. + Return TransferTrivia(node, ExpressionStatement(InvocationExpression(VisitIdentifierName(node.Name), VisitArgumentList(node.ArgumentList)))) + + End Function + + Public Overrides Function VisitRangeArgument(node As VB.Syntax.RangeArgumentSyntax) As SyntaxNode + Return MyBase.VisitRangeArgument(node) + End Function + + Public Overrides Function VisitReDimStatement(node As VB.Syntax.ReDimStatementSyntax) As SyntaxNode + + Return NotImplementedStatement(node) + ' TODO: Implement rewrite to Array.CopyTo with new array creation. + Throw New NotImplementedException(node.ToString()) + + End Function + + Public Overrides Function VisitRegionDirectiveTrivia(node As VB.Syntax.RegionDirectiveTriviaSyntax) As SyntaxNode + + Return RegionDirectiveTrivia(isActive:=False).WithRegionKeyword(Token(SyntaxTriviaList.Create(CS.SyntaxFactory.ElasticMarker), CS.SyntaxKind.RegionKeyword, TriviaList(PreprocessingMessage(node.Name.ValueText)))) + + End Function + + Public Overrides Function VisitResumeStatement(node As VB.Syntax.ResumeStatementSyntax) As SyntaxNode + + Return NotImplementedStatement(node) + Throw New NotSupportedException("Resume statements.") + + End Function + + Public Overrides Function VisitReturnStatement(node As VB.Syntax.ReturnStatementSyntax) As SyntaxNode + + Return ReturnStatement(Visit(node.Expression)) + + End Function + + Public Overrides Function VisitSelectBlock(node As VB.Syntax.SelectBlockSyntax) As SyntaxNode + + ' TODO: Bind to expression to ensure it's of a type C# can switch on. + Return TransferTrivia(node, SwitchStatement(Visit(node.SelectStatement.Expression)).WithSections(List(VisitCaseBlocks(node.CaseBlocks)))) + + End Function + + Public Overrides Function VisitSelectClause(node As VB.Syntax.SelectClauseSyntax) As SyntaxNode + Return MyBase.VisitSelectClause(node) + End Function + + Public Overrides Function VisitSelectStatement(node As VB.Syntax.SelectStatementSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Public Overrides Function VisitSimpleArgument(node As VB.Syntax.SimpleArgumentSyntax) As SyntaxNode + + If node.IsNamed Then + If TypeOf node.Parent.Parent Is VB.Syntax.AttributeSyntax Then + Return AttributeArgument(Visit(node.Expression)).WithNameColon(NameColon(IdentifierName(VisitIdentifier(node.NameColonEquals.Name.Identifier)))) + Else + ' TODO: Bind to discover ByRef arguments. + Return CS.SyntaxFactory.Argument(Visit(node.Expression)).WithNameColon(NameColon(IdentifierName(VisitIdentifier(node.NameColonEquals.Name.Identifier)))) + End If + Else + If TypeOf node.Parent.Parent Is VB.Syntax.AttributeSyntax Then + Return AttributeArgument(Visit(node.Expression)) + Else + ' TODO: Bind to discover ByRef arguments. + Return CS.SyntaxFactory.Argument(Visit(node.Expression)) + End If + End If + + End Function + + Public Overrides Function VisitSimpleAsClause(node As VB.Syntax.SimpleAsClauseSyntax) As SyntaxNode + + If node Is Nothing Then Return Nothing + + Return Visit(node.Type) + + End Function + + Public Overrides Function VisitSingleLineIfStatement(node As VB.Syntax.SingleLineIfStatementSyntax) As SyntaxNode + + Dim elseOpt As CS.Syntax.ElseClauseSyntax = Nothing + + If node.ElseClause IsNot Nothing Then + elseOpt = ElseClause(Block(List(Visit(node.ElseClause.Statements)))) + End If + + Return TransferTrivia(node, IfStatement( + Visit(node.Condition), + Block(List(Visit(node.Statements)))) _ + .WithElse(elseOpt) + ) + + + End Function + + Public Overrides Function VisitSingleLineLambdaExpression(node As VB.Syntax.SingleLineLambdaExpressionSyntax) As SyntaxNode + + Dim asyncKeyword = If(node.SubOrFunctionHeader.Modifiers.Any(VB.SyntaxKind.AsyncKeyword), + Token(CS.SyntaxKind.AsyncKeyword), + Nothing) + + Dim parameterList = VisitParameterList(node.SubOrFunctionHeader.ParameterList) + + Dim arrowToken = Token(CS.SyntaxKind.EqualsGreaterThanToken) + + Dim body = If(node.IsKind(VB.SyntaxKind.SingleLineFunctionLambdaExpression), + Visit(node.Body), + Block(SingletonList(Visit(node.Body)))) + + Return ParenthesizedLambdaExpression(asyncKeyword, parameterList, arrowToken, body) + + End Function + + Public Overrides Function VisitSkippedTokensTrivia(node As VB.Syntax.SkippedTokensTriviaSyntax) As SyntaxNode + + Return SkippedTokensTrivia(TokenList(MissingToken(CS.SyntaxKind.SemicolonToken).WithTrailingTrivia(TriviaList(Comment("/* " & node.ToString() & " */"))))) + + End Function + + Public Overrides Function VisitSpecialConstraint(node As VB.Syntax.SpecialConstraintSyntax) As SyntaxNode + + Select Case node.Kind + Case VB.SyntaxKind.NewConstraint + Return ConstructorConstraint() + Case VB.SyntaxKind.ClassConstraint + Return ClassOrStructConstraint(CS.SyntaxKind.ClassConstraint) + Case VB.SyntaxKind.StructureConstraint + Return ClassOrStructConstraint(CS.SyntaxKind.StructConstraint) + Case Else + Throw New NotSupportedException(node.Kind.ToString()) + End Select + + End Function + + Public Overrides Function VisitStopOrEndStatement(node As VB.Syntax.StopOrEndStatementSyntax) As SyntaxNode + + Return NotImplementedStatement(node) + ' TODO: Rewrite Stop to System.Diagnostics.Debug.Break and End to System.Environment.Exit. + Throw New NotImplementedException(node.ToString()) + + End Function + + Public Overrides Function VisitSyncLockBlock(node As VB.Syntax.SyncLockBlockSyntax) As SyntaxNode + + Return VisitSyncLockStatement(node.SyncLockStatement) + + End Function + + Public Overrides Function VisitSyncLockStatement(node As VB.Syntax.SyncLockStatementSyntax) As SyntaxNode + + Dim syncLockBlock As VB.Syntax.SyncLockBlockSyntax = node.Parent + + Return LockStatement(Visit(node.Expression), Block(List(Visit(syncLockBlock.Statements)))) + + End Function + + Public Overrides Function VisitTernaryConditionalExpression(node As VB.Syntax.TernaryConditionalExpressionSyntax) As SyntaxNode + + Return ConditionalExpression(Visit(node.Condition), Visit(node.WhenTrue), Visit(node.WhenFalse)) + + End Function + + Public Overrides Function VisitThrowStatement(node As VB.Syntax.ThrowStatementSyntax) As SyntaxNode + + If node.Expression Is Nothing Then + Return ThrowStatement() + Else + Return ThrowStatement(Visit(node.Expression)) + End If + + End Function + + Protected Function VisitTrivia(trivia As SyntaxTrivia) As SyntaxTrivia + + Dim text = trivia.ToFullString() + + Select Case trivia.Kind + Case VB.SyntaxKind.CommentTrivia + + If text.StartsWith("'") AndAlso text.Length > 1 Then + Return Comment("//" & text.Substring(1)) + ElseIf text.StartsWith("REM", StringComparison.OrdinalIgnoreCase) AndAlso text.Length > 3 Then + Return Comment("//" & text.Substring(3)) + Else + Return Comment("//") + End If + + Case VB.SyntaxKind.DisabledTextTrivia + + Return Comment("/* Disabled: " & text & " */") + + Case VB.SyntaxKind.EndOfLineTrivia + + Return EndOfLine(text) + + Case VB.SyntaxKind.DocumentationCommentTrivia + + Return CS.SyntaxFactory.Trivia(VisitDocumentationCommentTrivia(trivia.GetStructure())) + + Case VB.SyntaxKind.WhitespaceTrivia + + Return Whitespace(text) + + Case Else + + Return Comment("/* " & text & " */") + + End Select + End Function + + Protected Function VisitTrivia(trivia As IEnumerable(Of SyntaxTrivia)) As SyntaxTriviaList + + Return TriviaList(From t In trivia Select VisitTrivia(t)) + + End Function + + Public Overrides Function VisitTryBlock(node As VB.Syntax.TryBlockSyntax) As SyntaxNode + + Return TransferTrivia(node, TryStatement(List(VisitCatchBlocks(node.CatchBlocks))) _ + .WithBlock(Block(List(Visit(node.Statements)))) _ + .WithFinally(VisitFinallyBlock(node.FinallyBlock)) + ) + + End Function + + Public Overrides Function VisitTryStatement(node As VB.Syntax.TryStatementSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Public Overrides Function VisitTypeArgumentList(node As VB.Syntax.TypeArgumentListSyntax) As SyntaxNode + + Return TypeArgumentList(SeparatedList(Visit(node.Arguments).Cast(Of CS.Syntax.TypeSyntax))) + + End Function + + Public Overrides Function VisitModuleBlock(ByVal node As VB.Syntax.ModuleBlockSyntax) As SyntaxNode + + Return VisitModuleStatement(node.ModuleStatement) + + End Function + + Public Overrides Function VisitClassBlock(ByVal node As VB.Syntax.ClassBlockSyntax) As SyntaxNode + + Return VisitClassStatement(node.ClassStatement) + + End Function + + Public Overrides Function VisitStructureBlock(ByVal node As VB.Syntax.StructureBlockSyntax) As SyntaxNode + + Return VisitStructureStatement(node.StructureStatement) + + End Function + + Public Overrides Function VisitInterfaceBlock(ByVal node As VB.Syntax.InterfaceBlockSyntax) As SyntaxNode + + Return VisitInterfaceStatement(node.InterfaceStatement) + + End Function + + Public Overrides Function VisitTypeConstraint(ByVal node As VB.Syntax.TypeConstraintSyntax) As SyntaxNode + + Return TypeConstraint(Visit(node.Type)) + + End Function + + Public Overrides Function VisitTypeOfExpression(node As VB.Syntax.TypeOfExpressionSyntax) As SyntaxNode + + Dim isExpression = BinaryExpression(CS.SyntaxKind.IsExpression, Visit(node.Expression), Visit(node.Type)) + + If node.IsKind(VB.SyntaxKind.TypeOfIsNotExpression) Then + Return PrefixUnaryExpression(CS.SyntaxKind.LogicalNotExpression, + ParenthesizedExpression(isExpression) + ) + Else + Return isExpression + End If + + End Function + + Public Overrides Function VisitTypeParameter(node As VB.Syntax.TypeParameterSyntax) As SyntaxNode + + Dim varianceKeyword As SyntaxToken + Select Case node.VarianceKeyword.Kind + Case VB.SyntaxKind.InKeyword + varianceKeyword = Token(CS.SyntaxKind.InKeyword) + + Case VB.SyntaxKind.OutKeyword + varianceKeyword = Token(CS.SyntaxKind.OutKeyword) + + Case Else + varianceKeyword = Token(CS.SyntaxKind.None) + End Select + + Return TypeParameter(VisitIdentifier(node.Identifier)).WithVarianceKeyword(varianceKeyword) + + End Function + + Public Overrides Function VisitTypeParameterList(node As VB.Syntax.TypeParameterListSyntax) As SyntaxNode + + If node Is Nothing Then Return Nothing + + Return TypeParameterList(SeparatedList(Visit(node.Parameters).Cast(Of CS.Syntax.TypeParameterSyntax))) + + End Function + + Protected Function VisitTypeParameterConstraintClauses(typeParameterListOpt As VB.Syntax.TypeParameterListSyntax) As IEnumerable(Of SyntaxNode) + + If typeParameterListOpt Is Nothing Then Return Nothing + + Return Visit((From parameter In typeParameterListOpt.Parameters Where parameter.TypeParameterConstraintClause IsNot Nothing Select parameter.TypeParameterConstraintClause)) + + End Function + + Public Overrides Function VisitTypeParameterMultipleConstraintClause(node As VB.Syntax.TypeParameterMultipleConstraintClauseSyntax) As SyntaxNode + + If node Is Nothing Then Return Nothing + + ' In C# the new() constraint must be specified last. + Return TypeParameterConstraintClause(IdentifierName(VisitIdentifier(CType(node.Parent, VB.Syntax.TypeParameterSyntax).Identifier))).WithConstraints(SeparatedList(Visit(From c In node.Constraints Order By c.IsKind(VB.SyntaxKind.NewConstraint)).Cast(Of CS.Syntax.TypeParameterConstraintSyntax))) + + End Function + + Public Overrides Function VisitTypeParameterSingleConstraintClause(node As VB.Syntax.TypeParameterSingleConstraintClauseSyntax) As SyntaxNode + + If node Is Nothing Then Return Nothing + + Return TypeParameterConstraintClause(IdentifierName(VisitIdentifier(CType(node.Parent, VB.Syntax.TypeParameterSyntax).Identifier))).WithConstraints(SingletonSeparatedList(CType(Visit(node.Constraint), CS.Syntax.TypeParameterConstraintSyntax))) + + End Function + + Public Overrides Function VisitModuleStatement(ByVal node As VB.Syntax.ModuleStatementSyntax) As SyntaxNode + Dim block As VB.Syntax.ModuleBlockSyntax = node.Parent + + ' TODO: Rewrite all members in a module to be static. + Return TransferTrivia(block, ClassDeclaration(VisitIdentifier(node.Identifier)) _ + .WithAttributeLists(List(VisitAttributeLists(node.AttributeLists))) _ + .WithModifiers(TokenList(VisitModifiers(node.Modifiers))) _ + .WithTypeParameterList(VisitTypeParameterList(node.TypeParameterList)) _ + .WithConstraintClauses(List(VisitTypeParameterConstraintClauses(node.TypeParameterList))) _ + .WithMembers(List(Visit(block.Members))) + ) + + End Function + + Public Overrides Function VisitClassStatement(ByVal node As VB.Syntax.ClassStatementSyntax) As SyntaxNode + + Dim block As VB.Syntax.ClassBlockSyntax = node.Parent + + Dim bases As CS.Syntax.BaseListSyntax = Nothing + If block.Inherits.Count > 0 OrElse block.Implements.Count > 0 Then + bases = BaseList(SeparatedList(Of BaseTypeSyntax)(VisitInheritsStatements(block.Inherits).Union(VisitImplementsStatements(block.Implements)). + Cast(Of CS.Syntax.TypeSyntax).Select(Function(t) SimpleBaseType(t)))) + End If + + Return TransferTrivia(block, ClassDeclaration(VisitIdentifier(node.Identifier)) _ + .WithAttributeLists(List(VisitAttributeLists(node.AttributeLists))) _ + .WithModifiers(TokenList(VisitModifiers(node.Modifiers))) _ + .WithTypeParameterList(VisitTypeParameterList(node.TypeParameterList)) _ + .WithBaseList(bases) _ + .WithConstraintClauses(List(VisitTypeParameterConstraintClauses(node.TypeParameterList))) _ + .WithMembers(List(Visit(block.Members))) + ) + + End Function + + Public Overrides Function VisitStructureStatement(ByVal node As VB.Syntax.StructureStatementSyntax) As SyntaxNode + + Dim block As VB.Syntax.StructureBlockSyntax = node.Parent + + Dim bases As CS.Syntax.BaseListSyntax = Nothing + If block.Inherits.Count > 0 OrElse block.Implements.Count > 0 Then + bases = BaseList(SeparatedList(Of BaseTypeSyntax)(VisitInheritsStatements(block.Inherits).Union(VisitImplementsStatements(block.Implements)). + Cast(Of CS.Syntax.TypeSyntax).Select(Function(t) SimpleBaseType(t)))) + End If + + Return TransferTrivia(block, StructDeclaration(VisitIdentifier(node.Identifier)) _ + .WithAttributeLists(List(VisitAttributeLists(node.AttributeLists))) _ + .WithModifiers(TokenList(VisitModifiers(node.Modifiers))) _ + .WithTypeParameterList(VisitTypeParameterList(node.TypeParameterList)) _ + .WithBaseList(bases) _ + .WithConstraintClauses(List(VisitTypeParameterConstraintClauses(node.TypeParameterList))) _ + .WithMembers(List(Visit(block.Members))) + ) + + End Function + + Public Overrides Function VisitInterfaceStatement(ByVal node As VB.Syntax.InterfaceStatementSyntax) As SyntaxNode + + Dim block As VB.Syntax.InterfaceBlockSyntax = node.Parent + + Dim bases As CS.Syntax.BaseListSyntax = Nothing + If block.Inherits.Count > 0 Then + bases = BaseList(SeparatedList(Of BaseTypeSyntax)(VisitInheritsStatements(block.Inherits). + Cast(Of CS.Syntax.TypeSyntax).Select(Function(t) SimpleBaseType(t)))) + End If + + ' VB allows Interfaces to have nested types, C# does not. + ' But this is rare enough that we'll assume the members are non-types for now. + Return TransferTrivia(block, InterfaceDeclaration(VisitIdentifier(node.Identifier)) _ + .WithAttributeLists(List(VisitAttributeLists(node.AttributeLists))) _ + .WithModifiers(TokenList(VisitModifiers(node.Modifiers))) _ + .WithTypeParameterList(VisitTypeParameterList(node.TypeParameterList)) _ + .WithBaseList(bases) _ + .WithConstraintClauses(List(VisitTypeParameterConstraintClauses(node.TypeParameterList))) _ + .WithMembers(List(Visit(block.Members))) + ) + End Function + + Public Overrides Function VisitUnaryExpression(ByVal node As VB.Syntax.UnaryExpressionSyntax) As SyntaxNode + + Select Case node.Kind + Case VB.SyntaxKind.UnaryMinusExpression + + Return PrefixUnaryExpression(CS.SyntaxKind.UnaryMinusExpression, Visit(node.Operand)) + + Case VB.SyntaxKind.UnaryPlusExpression + + Return PrefixUnaryExpression(CS.SyntaxKind.UnaryPlusExpression, Visit(node.Operand)) + + Case VB.SyntaxKind.NotExpression + + ' TODO: Bind expression to determine whether this is a logical or bitwise not expression. + Return PrefixUnaryExpression(CS.SyntaxKind.LogicalNotExpression, Visit(node.Operand)) + + Case VB.SyntaxKind.AddressOfExpression + + Return Visit(node.Operand) + + Case Else + Throw New NotSupportedException(node.Kind.ToString()) + End Select + + End Function + + Public Overrides Function VisitUsingBlock(node As VB.Syntax.UsingBlockSyntax) As SyntaxNode + + Return VisitUsingStatement(node.UsingStatement) + + End Function + + Public Overrides Function VisitUsingStatement(node As VB.Syntax.UsingStatementSyntax) As SyntaxNode + + Dim usingBlock As VB.Syntax.UsingBlockSyntax = node.Parent + + Dim body As CS.Syntax.StatementSyntax = Block(List(Visit(usingBlock.Statements))) + + If node.Expression IsNot Nothing Then + + Return TransferTrivia(usingBlock, UsingStatement(body).WithExpression(Visit(node.Expression))) + + Else + + For i = node.Variables.Count - 1 To 0 Step -1 + + Dim declarator = node.Variables(i) + + ' TODO: Refactor so that visiting a VB declarator returns a C# declarator. + body = UsingStatement(body).WithDeclaration( + VariableDeclaration( + DeriveType(declarator.Names(0), declarator.AsClause, declarator.Initializer), + SingletonSeparatedList(VariableDeclarator( + VisitIdentifier(declarator.Names(0).Identifier)) _ + .WithInitializer(DeriveInitializer(declarator.Names(0), declarator.AsClause, declarator.Initializer)) + ) + ) + ) + + Next + + Return TransferTrivia(node, body) + End If + + End Function + + Public Overrides Function VisitVariableDeclarator(node As VB.Syntax.VariableDeclaratorSyntax) As SyntaxNode + + Throw New InvalidOperationException() + + End Function + + Protected Function VisitVariableDeclaratorVariables(declarator As VB.Syntax.VariableDeclaratorSyntax) As IEnumerable(Of SyntaxNode) + + ' TODO: Derive an initializer based on VB's As New syntax or default variable + ' initialization. + Select Case declarator.Parent.Kind + Case VB.SyntaxKind.FieldDeclaration + Dim field As VB.Syntax.FieldDeclarationSyntax = declarator.Parent + + Return From v In declarator.Names Select FieldDeclaration( + VariableDeclaration( + DeriveType(v, declarator.AsClause, declarator.Initializer), + SingletonSeparatedList(VariableDeclarator( + VisitIdentifier(v.Identifier)).WithInitializer( + DeriveInitializer(v, declarator.AsClause, declarator.Initializer) + ) + ) + ) + ).WithAttributeLists(List(VisitAttributeLists(field.AttributeLists))) _ + .WithModifiers(TokenList(VisitModifiers(field.Modifiers))) + Case VB.SyntaxKind.LocalDeclarationStatement + Dim local As VB.Syntax.LocalDeclarationStatementSyntax = declarator.Parent + + Return From v In declarator.Names Select LocalDeclarationStatement( + VariableDeclaration( + DeriveType(v, declarator.AsClause, declarator.Initializer), + SingletonSeparatedList( + VariableDeclarator( + VisitIdentifier(v.Identifier)).WithInitializer( + DeriveInitializer(v, declarator.AsClause, declarator.Initializer) + ) + ) + ) + ).WithModifiers(TokenList(VisitModifiers(local.Modifiers))) + + Case Else + Throw New NotSupportedException(declarator.Parent.Kind.ToString()) + End Select + End Function + + Public Overrides Function VisitVariableNameEquals(node As VB.Syntax.VariableNameEqualsSyntax) As SyntaxNode + Return MyBase.VisitVariableNameEquals(node) + End Function + + Public Overrides Function VisitWhereClause(node As VB.Syntax.WhereClauseSyntax) As SyntaxNode + + Return WhereClause(Visit(node.Condition)) + + End Function + + Public Overrides Function VisitWhileBlock(node As VB.Syntax.WhileBlockSyntax) As SyntaxNode + + Return VisitWhileStatement(node.WhileStatement) + + End Function + + Public Overrides Function VisitWhileStatement(node As VB.Syntax.WhileStatementSyntax) As SyntaxNode + + Dim whileBlock As VB.Syntax.WhileBlockSyntax = node.Parent + + Return TransferTrivia(node, WhileStatement(Visit(node.Condition), Block(List(Visit(whileBlock.Statements))))) + + End Function + + Public Overrides Function VisitWhileOrUntilClause(node As VB.Syntax.WhileOrUntilClauseSyntax) As SyntaxNode + + If node Is Nothing Then Return Nothing + + If node.IsKind(VB.SyntaxKind.WhileClause) Then + Return Visit(node.Condition) + Else + ' TODO: Invert conditionals if possible on comparison expressions to avoid wrapping this in a !expression. + Return PrefixUnaryExpression(CS.SyntaxKind.LogicalNotExpression, ParenthesizedExpression(Visit(node.Condition))) + End If + + End Function + + Public Overrides Function VisitWithBlock(node As VB.Syntax.WithBlockSyntax) As SyntaxNode + + Return VisitWithStatement(node.WithStatement) + + End Function + + Public Overrides Function VisitWithStatement(node As VB.Syntax.WithStatementSyntax) As SyntaxNode + + Return NotImplementedStatement(node) + ' TODO: Rewrite to block with temp variable name instead of omitted LeftOpt member access expressions. + Throw New NotImplementedException(node.ToString()) + + End Function + + Public Overrides Function VisitXmlAttribute(node As VB.Syntax.XmlAttributeSyntax) As SyntaxNode + Return MyBase.VisitXmlAttribute(node) + End Function + + Public Overrides Function VisitXmlBracketedName(node As VB.Syntax.XmlBracketedNameSyntax) As SyntaxNode + Return MyBase.VisitXmlBracketedName(node) + End Function + + Public Overrides Function VisitXmlCDataSection(node As VB.Syntax.XmlCDataSectionSyntax) As SyntaxNode + Return VisitXmlNode(node) + End Function + + Public Overrides Function VisitXmlComment(node As VB.Syntax.XmlCommentSyntax) As SyntaxNode + Return VisitXmlNode(node) + End Function + + Public Overrides Function VisitXmlDeclaration(node As VB.Syntax.XmlDeclarationSyntax) As SyntaxNode + Return MyBase.VisitXmlDeclaration(node) + End Function + + Public Overrides Function VisitXmlDeclarationOption(node As VB.Syntax.XmlDeclarationOptionSyntax) As SyntaxNode + Return MyBase.VisitXmlDeclarationOption(node) + End Function + + Public Overrides Function VisitXmlDocument(node As VB.Syntax.XmlDocumentSyntax) As SyntaxNode + Return VisitXmlNode(node) + End Function + + Public Overrides Function VisitXmlElement(node As VB.Syntax.XmlElementSyntax) As SyntaxNode + Return VisitXmlNode(node) + End Function + + Public Overrides Function VisitXmlElementEndTag(node As VB.Syntax.XmlElementEndTagSyntax) As SyntaxNode + Return MyBase.VisitXmlElementEndTag(node) + End Function + + Public Overrides Function VisitXmlElementStartTag(node As VB.Syntax.XmlElementStartTagSyntax) As SyntaxNode + Return MyBase.VisitXmlElementStartTag(node) + End Function + + Public Overrides Function VisitXmlEmbeddedExpression(node As VB.Syntax.XmlEmbeddedExpressionSyntax) As SyntaxNode + Return MyBase.VisitXmlEmbeddedExpression(node) + End Function + + Public Overrides Function VisitXmlEmptyElement(node As VB.Syntax.XmlEmptyElementSyntax) As SyntaxNode + Return VisitXmlNode(node) + End Function + + Public Overrides Function VisitXmlMemberAccessExpression(node As VB.Syntax.XmlMemberAccessExpressionSyntax) As SyntaxNode + Return NotImplementedExpression(node) + End Function + + Public Overrides Function VisitXmlName(node As VB.Syntax.XmlNameSyntax) As SyntaxNode + Return MyBase.VisitXmlName(node) + End Function + + Public Overrides Function VisitXmlNamespaceImportsClause(node As VB.Syntax.XmlNamespaceImportsClauseSyntax) As SyntaxNode + + Return UsingDirective(IdentifierName(MissingToken(CS.SyntaxKind.IdentifierToken))) _ + .WithUsingKeyword(MissingToken(CS.SyntaxKind.UsingKeyword)) _ + .WithSemicolonToken(MissingSemicolonToken.WithTrailingTrivia(TriviaList(Comment("/* " & node.ToString() & " */")))) + + End Function + + Protected Overridable Function VisitXmlNode(node As VB.Syntax.XmlNodeSyntax) As SyntaxNode + ' Just spit this out as a string literal for now. + Dim text = node.ToString().Replace("""", """""") + + Return LiteralExpression(CS.SyntaxKind.StringLiteralExpression, Literal("@""" & text & """", text)) + End Function + + Public Overrides Function VisitXmlPrefix(node As VB.Syntax.XmlPrefixSyntax) As SyntaxNode + Return MyBase.VisitXmlPrefix(node) + End Function + + Public Overrides Function VisitXmlProcessingInstruction(node As VB.Syntax.XmlProcessingInstructionSyntax) As SyntaxNode + Return VisitXmlNode(node) + End Function + + Public Overrides Function VisitXmlString(node As VB.Syntax.XmlStringSyntax) As SyntaxNode + Return MyBase.VisitXmlString(node) + End Function + + Public Overrides Function VisitXmlText(node As VB.Syntax.XmlTextSyntax) As SyntaxNode + Return MyBase.VisitXmlText(node) + End Function + + Protected Function NotImplementedStatement(node As SyntaxNode) As CS.Syntax.StatementSyntax + Return EmptyStatement(MissingSemicolonToken.WithTrailingTrivia(TriviaList(Comment("/* Not Implemented: " & node.ToString() & " */")))) + End Function + + Protected Function NotImplementedMember(node As SyntaxNode) As CS.Syntax.MemberDeclarationSyntax + Return IncompleteMember().WithModifiers(TokenList(MissingToken(CS.SyntaxKind.PublicKeyword).WithTrailingTrivia(TriviaList(Comment("/* Not Implemented: " & node.ToString() & " */"))))) + End Function + + Protected Function NotImplementedExpression(node As SyntaxNode) As CS.Syntax.ExpressionSyntax + Return IdentifierName(MissingToken(CS.SyntaxKind.IdentifierToken).WithTrailingTrivia(TriviaList(Comment("/* Not Implemented: " & node.ToString() & " */")))) + End Function + + Protected Function NotImplementedModifier(token As SyntaxToken) As SyntaxToken + Return MissingToken(CS.SyntaxKind.PublicKeyword).WithTrailingTrivia(TriviaList(Comment("/* Not Implemented: " & token.ToString() & " */"))) + End Function + + End Class + + End Class + + Friend Module SyntaxUtils + + ReadOnly CommaToken As SyntaxToken = Token(CS.SyntaxKind.CommaToken) + ReadOnly OmittedArraySizeExpression As SyntaxNode = CS.SyntaxFactory.OmittedArraySizeExpression(Token(CS.SyntaxKind.OmittedArraySizeExpressionToken)) + + Public Function OmittedArraySizeExpressionList(Of TNode As SyntaxNode)(rank As Integer) As SeparatedSyntaxList(Of TNode) + + Dim tokens = New SyntaxNodeOrToken(0 To 2 * rank - 2) {} + For i = 0 To rank - 2 + tokens(2 * i) = OmittedArraySizeExpression + tokens(2 * i + 1) = CommaToken + Next + + tokens(2 * rank - 2) = OmittedArraySizeExpression + + Return SeparatedList(Of TNode)(tokens) + + End Function + + + Public Function WithLeadingTrivia(Of TNode As SyntaxNode)(node As TNode, trivia As IEnumerable(Of SyntaxTrivia)) As TNode + Dim firstToken = node.GetFirstToken() + + Return node.ReplaceToken(firstToken, firstToken.WithLeadingTrivia(TriviaList(trivia))) + End Function + + + Public Function WithTrailingTrivia(Of TNode As SyntaxNode)(node As TNode, trivia As IEnumerable(Of SyntaxTrivia)) As TNode + Dim lastToken = node.GetLastToken() + + Return node.ReplaceToken(lastToken, lastToken.WithTrailingTrivia(TriviaList(trivia))) + End Function + + + Public Function WithTrivia(Of TNode As SyntaxNode)(node As TNode, leadingTrivia As IEnumerable(Of SyntaxTrivia), trailingTrivia As IEnumerable(Of SyntaxTrivia)) As TNode + Return node.WithLeadingTrivia(leadingTrivia).WithTrailingTrivia(trailingTrivia) + End Function + + End Module + +End Namespace diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Lib/QueryClauseConvertingVisitor.vb b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Lib/QueryClauseConvertingVisitor.vb new file mode 100644 index 000000000..089f166f0 --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Lib/QueryClauseConvertingVisitor.vb @@ -0,0 +1,304 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Option Strict Off + +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CSharp +Imports Microsoft.CodeAnalysis.CSharp.SyntaxFactory +Imports Microsoft.CodeAnalysis.VisualBasic +Imports CS = Microsoft.CodeAnalysis.CSharp +Imports VB = Microsoft.CodeAnalysis.VisualBasic + +Namespace VisualBasicToCSharpConverter + + Partial Public Class Converter + + Partial Private Class NodeConvertingVisitor + + Public Class QueryClauseConvertingVisitor + Inherits VisualBasicSyntaxVisitor(Of Object) + + Private ReadOnly Parent As NodeConvertingVisitor + + Private IsFirstAfterSelect As Boolean + Private InitialClause As CS.Syntax.FromClauseSyntax + Private Clauses As New List(Of CS.Syntax.QueryClauseSyntax)() + Private Expression As CS.Syntax.ExpressionSyntax + Private RangeVariablesInScope As New List(Of String)() + Private SelectOrGroupClause As CS.Syntax.SelectOrGroupClauseSyntax + Private Continuation As CS.Syntax.QueryContinuationSyntax + + Public Sub New(parent As NodeConvertingVisitor) + Me.Parent = parent + End Sub + + Public Overrides Function VisitFromClause(node As VB.Syntax.FromClauseSyntax) As Object + + ' TODO: Implement call to .Cast or .Select with cast if type of As Clause doesn't match element type of source. + For Each crv In node.Variables + Dim clause = FromClause(Parent.VisitIdentifier(crv.Identifier.Identifier), Parent.Visit(crv.Expression)).WithType(Parent.DeriveType(crv)) + If (InitialClause Is Nothing) Then + InitialClause = clause + Else + Clauses.Add(clause) + End If + + RangeVariablesInScope.Add(crv.Identifier.Identifier.ValueText) + Next + + Return Nothing + End Function + + Public Overrides Function VisitLetClause(node As VB.Syntax.LetClauseSyntax) As Object + + For Each erv In node.Variables + Clauses.Add(LetClause(Parent.VisitIdentifier(erv.NameEquals.Identifier.Identifier), Parent.Visit(erv.Expression))) + + RangeVariablesInScope.Add(erv.NameEquals.Identifier.Identifier.ValueText) + Next + + Return Nothing + End Function + + Public Overrides Function VisitAggregateClause(node As VB.Syntax.AggregateClauseSyntax) As Object + + Return WhereClause(MissingToken(CS.SyntaxKind.WhereKeyword), Parent.NotImplementedExpression(node)) + + Throw New NotImplementedException(node.ToString()) + End Function + + Public Overrides Function VisitDistinctClause(node As VB.Syntax.DistinctClauseSyntax) As Object + Throw New InvalidOperationException() + End Function + + Public Overrides Function VisitWhereClause(node As VB.Syntax.WhereClauseSyntax) As Object + + Clauses.Add(WhereClause(Parent.Visit(node.Condition))) + + Return Nothing + End Function + + ' Take While, Skip While + Public Overrides Function VisitPartitionWhileClause(node As VB.Syntax.PartitionWhileClauseSyntax) As Object + Return WhereClause(MissingToken(CS.SyntaxKind.WhereKeyword), Parent.NotImplementedExpression(node)) + + Throw New NotImplementedException(node.ToString()) + End Function + + ' Take, Skip + Public Overrides Function VisitPartitionClause(node As VB.Syntax.PartitionClauseSyntax) As Object + Return WhereClause(MissingToken(CS.SyntaxKind.WhereKeyword), Parent.NotImplementedExpression(node)) + + Throw New NotImplementedException(node.ToString()) + End Function + + Public Overrides Function VisitGroupByClause(node As VB.Syntax.GroupByClauseSyntax) As Object + Return WhereClause(MissingToken(CS.SyntaxKind.WhereKeyword), Parent.NotImplementedExpression(node)) + + Throw New NotImplementedException(node.ToString()) + + End Function + + Public Overrides Function VisitSimpleJoinClause(node As VB.Syntax.SimpleJoinClauseSyntax) As Object + + If node.AdditionalJoins.Count > 0 Then Return WhereClause(MissingToken(CS.SyntaxKind.WhereKeyword), Parent.NotImplementedExpression(node)) 'Throw New NotImplementedException("Joins with additional nested joins.") + If node.JoinConditions.Count > 1 Then Return WhereClause(MissingToken(CS.SyntaxKind.WhereKeyword), Parent.NotImplementedExpression(node)) ' Throw New NotImplementedException("Joins with multiple conditions.") + + Clauses.Add(JoinClause( + Parent.VisitIdentifier(node.JoinedVariables(0).Identifier.Identifier), + Parent.Visit(node.JoinedVariables(0).Expression), + Parent.Visit(node.JoinConditions(0).Left), + Parent.Visit(node.JoinConditions(0).Right) + ).WithType(Parent.DeriveType(node.JoinedVariables(0))) + ) + + RangeVariablesInScope.Add(node.JoinedVariables(0).Identifier.Identifier.Value) + + Return Nothing + End Function + + Public Overrides Function VisitGroupJoinClause(node As VB.Syntax.GroupJoinClauseSyntax) As Object + Return WhereClause(MissingToken(CS.SyntaxKind.WhereKeyword), Parent.NotImplementedExpression(node)) + + Throw New NotImplementedException(node.ToString()) + End Function + + Public Overrides Function VisitOrderByClause(node As VB.Syntax.OrderByClauseSyntax) As Object + + Clauses.Add(OrderByClause(SeparatedList(VisitOrderings(node.Orderings)))) + + Return Nothing + End Function + + Protected Shadows Function VisitOrdering(node As VB.Syntax.OrderingSyntax) As CS.Syntax.OrderingSyntax + + If node.IsKind(VB.SyntaxKind.AscendingOrdering) Then + Return Ordering(CS.SyntaxKind.AscendingOrdering, Parent.Visit(node.Expression)) + Else + Return Ordering(CS.SyntaxKind.DescendingOrdering, Parent.Visit(node.Expression)) + End If + + End Function + + Protected Function VisitOrderings(nodes As IEnumerable(Of VB.Syntax.OrderingSyntax)) As IEnumerable(Of CS.Syntax.OrderingSyntax) + + Return From node In nodes Select VisitOrdering(node) + + End Function + + + Public Overrides Function VisitSelectClause(node As VB.Syntax.SelectClauseSyntax) As Object + + Dim variables As New List(Of CS.Syntax.ExpressionSyntax)() + + RangeVariablesInScope.Clear() + If node.Variables.Count = 1 Then + + SelectOrGroupClause = SelectClause(Parent.Visit(node.Variables(0).Expression)) + + RangeVariablesInScope.Add(DeriveRangeVariableName(node.Variables(0))) + Else + + For Each v In node.Variables + + If v.NameEquals IsNot Nothing Then + variables.Add(AssignmentExpression(CS.SyntaxKind.SimpleAssignmentExpression, IdentifierName(Parent.VisitIdentifier(v.NameEquals.Identifier.Identifier)), Parent.Visit(v.Expression))) + Else + variables.Add(Parent.Visit(v.Expression)) + End If + + RangeVariablesInScope.Add(DeriveRangeVariableName(v)) + Next + + SelectOrGroupClause = SelectClause( + AnonymousObjectCreationExpression( + SeparatedList(From variable In variables Select AnonymousObjectMemberDeclarator(variable)) + ) + ) + End If + + IsFirstAfterSelect = True + + Return Nothing + End Function + + Public Overrides Function VisitQueryExpression(node As VB.Syntax.QueryExpressionSyntax) As Object + + Dim clauses = node.Clauses + + Dim aggregate = TryCast(clauses.First, VB.Syntax.AggregateClauseSyntax) + + If aggregate IsNot Nothing Then + + Return Parent.NotImplementedExpression(node) + + Throw New NotImplementedException(node.ToString()) + + clauses = aggregate.AdditionalQueryOperators + + Else + + For Each c In clauses + + If c.IsKind(VB.SyntaxKind.DistinctClause) Then + + EndQuery() + + Expression = InvocationExpression( + MemberAccessExpression( + CS.SyntaxKind.SimpleMemberAccessExpression, + ParenthesizedExpression(Expression), + IdentifierName("Distinct") + ), + ArgumentList() + ) + + Continue For + + ElseIf IsFirstAfterSelect Then + + EndQuery() + + BringRangeVariablesIntoScope() + + IsFirstAfterSelect = False + + End If + + Visit(c) + + Next + + EndQuery() + End If + + Return Expression + End Function + + Protected Function DeriveRangeVariableName(variable As VB.Syntax.ExpressionRangeVariableSyntax) As String + + If variable.NameEquals IsNot Nothing Then + Return variable.NameEquals.Identifier.Identifier.ValueText + End If + + Return Parent.DeriveName(variable.Expression) + + End Function + + Private Sub EndQuery() + + ' This means the query terminated in an aggregate. + If InitialClause Is Nothing And Clauses.Count = 0 Then Return + + ' If this query omitted the Select clause, synthesize it. + If Not IsFirstAfterSelect Then + + If RangeVariablesInScope.Count = 1 Then + + SelectOrGroupClause = SelectClause(IdentifierName(RangeVariablesInScope(0))) + + Else + SelectOrGroupClause = SelectClause( + AnonymousObjectCreationExpression( + SeparatedList(From n In RangeVariablesInScope + Select AnonymousObjectMemberDeclarator(IdentifierName(n))) + ) + ) + End If + + IsFirstAfterSelect = True + End If + + Expression = QueryExpression(InitialClause, QueryBody(List(Clauses), SelectOrGroupClause, Continuation)) + + Clauses.Clear() + InitialClause = Nothing + SelectOrGroupClause = Nothing + IsFirstAfterSelect = False + Continuation = Nothing + End Sub + + Private Sub BringRangeVariablesIntoScope() + + If RangeVariablesInScope.Count = 1 Then + + Clauses.Add(FromClause(Identifier(RangeVariablesInScope(0)), Expression)) + + Else + Clauses.Add(FromClause(Identifier("_"), Expression)) + + For Each v In RangeVariablesInScope + Clauses.Add(LetClause(Identifier(v), MemberAccessExpression(CS.SyntaxKind.SimpleMemberAccessExpression, IdentifierName("_"), IdentifierName(v)))) + Next + End If + + Expression = Nothing + End Sub + + End Class + + End Class + + End Class + +End Namespace diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Lib/VisualBasicToCSharpConverter.Lib.vbproj b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Lib/VisualBasicToCSharpConverter.Lib.vbproj new file mode 100644 index 000000000..c7d866de5 --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Lib/VisualBasicToCSharpConverter.Lib.vbproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + + + + + + + + + diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/Resources.Designer.vb b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/Resources.Designer.vb new file mode 100644 index 000000000..23b3b0f55 --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/Resources.Designer.vb @@ -0,0 +1,94 @@ +'------------------------------------------------------------------------------ +' +' This code was generated by a tool. +' Runtime Version:4.0.30319.42000 +' +' Changes to this file may cause incorrect behavior and will be lost if +' the code is regenerated. +' +'------------------------------------------------------------------------------ + +Option Strict On +Option Explicit On + +Imports System + +Namespace My.Resources + + 'This class was auto-generated by the StronglyTypedResourceBuilder + 'class via a tool like ResGen or Visual Studio. + 'To add or remove a member, edit your .ResX file then rerun ResGen + 'with the /str option, or rebuild your VS project. + ''' + ''' A strongly-typed resource class, for looking up localized strings, etc. + ''' + _ + Friend Module Resources + + Private resourceMan As Global.System.Resources.ResourceManager + + Private resourceCulture As Global.System.Globalization.CultureInfo + + ''' + ''' Returns the cached ResourceManager instance used by this class. + ''' + _ + Friend ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager + Get + If Object.ReferenceEquals(resourceMan, Nothing) Then + Dim temp As Global.System.Resources.ResourceManager = New Global.System.Resources.ResourceManager("VisualBasicToCSharpConverter.UnitTests.Resources", GetType(Resources).Assembly) + resourceMan = temp + End If + Return resourceMan + End Get + End Property + + ''' + ''' Overrides the current thread's CurrentUICulture property for all + ''' resource lookups using this strongly typed resource class. + ''' + _ + Friend Property Culture() As Global.System.Globalization.CultureInfo + Get + Return resourceCulture + End Get + Set + resourceCulture = value + End Set + End Property + + ''' + ''' Looks up a localized string similar to Option Infer On + '''Option Explicit Off + '''Imports System + '''Imports System.Collections.Generic + '''Imports System.Linq + '''Imports System.Linq.Expressions + '''Imports System.Text + '''Imports M = System.Math + '''Imports System.Collections + '''Imports <xmlns:ns="foo"> + '''Imports <xmlns="foo"> + '''#Const line = 6 + '''#Const foo = True + '''#If foo Then + '''#Else + '''#End If + '''' There is no equivalent to #undef in VB.NET: + ''''#undef foo + ''''#warning foo + ''''#error foo + '''' There is no equivalent to 'extern alias' in VB: + ''''extern alias Foo; + '''#If DEBUG OrElse TRA [rest of string was truncated]";. + ''' + Friend ReadOnly Property VBAllInOne() As String + Get + Return ResourceManager.GetString("VBAllInOne", resourceCulture) + End Get + End Property + End Module +End Namespace diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/Resources.resx b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/Resources.resx new file mode 100644 index 000000000..bc35f4e49 --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + ..\Resources\VBAllInOne.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + \ No newline at end of file diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.cs.xlf b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.cs.xlf new file mode 100644 index 000000000..99bb7f1d7 --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.cs.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.de.xlf b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.de.xlf new file mode 100644 index 000000000..2b1fd700e --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.de.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.es.xlf b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.es.xlf new file mode 100644 index 000000000..a4f8872fd --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.es.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.fr.xlf b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.fr.xlf new file mode 100644 index 000000000..8231293ba --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.fr.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.it.xlf b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.it.xlf new file mode 100644 index 000000000..86dbb08ee --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.it.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.ja.xlf b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.ja.xlf new file mode 100644 index 000000000..737849421 --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.ja.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.ko.xlf b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.ko.xlf new file mode 100644 index 000000000..4997d8516 --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.ko.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.pl.xlf b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.pl.xlf new file mode 100644 index 000000000..b0181a3f5 --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.pl.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.pt-BR.xlf b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.pt-BR.xlf new file mode 100644 index 000000000..56a8322a3 --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.pt-BR.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.ru.xlf b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.ru.xlf new file mode 100644 index 000000000..7f33dd37b --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.ru.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.tr.xlf b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.tr.xlf new file mode 100644 index 000000000..cb11b99f6 --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.tr.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.zh-Hans.xlf b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.zh-Hans.xlf new file mode 100644 index 000000000..d831e742d --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.zh-Hans.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.zh-Hant.xlf b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.zh-Hant.xlf new file mode 100644 index 000000000..a0c048744 --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/My Project/xlf/Resources.zh-Hant.xlf @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/Resources/VBAllInOne.txt b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/Resources/VBAllInOne.txt new file mode 100644 index 000000000..d22e3d2bf --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/Resources/VBAllInOne.txt @@ -0,0 +1,1038 @@ +Option Infer On +Option Explicit Off +Imports System +Imports System.Collections.Generic +Imports System.Linq +Imports System.Linq.Expressions +Imports System.Text +Imports M = System.Math +Imports System.Collections +Imports +Imports +#Const line = 6 +#Const foo = True +#If foo Then +#Else +#End If +' There is no equivalent to #undef in VB.NET: +'#undef foo +'#warning foo +'#error foo +' There is no equivalent to 'extern alias' in VB: +'extern alias Foo; +#If DEBUG OrElse TRACE Then +Imports System.Diagnostics +#ElseIf SILVERLIGHT Then +Imports System.Diagnostics +#Else +Imports System.Diagnostics +#End If +#Region "Region" +#Region "more" +Imports ConsoleApplication2.Test +#End Region +Imports X = int1 +Imports X = ABC.X(Of Integer) +Imports A.B + +#End Region + + +Friend Interface CoContra(Of Out T, In K) + +End Interface + +Public Delegate Sub CoContra2() +Namespace My + + Friend Interface CoContra(Of Out T, In K) + + End Interface + + Friend Delegate Sub CoContra2(Of Out T, In K)() + + Partial Public Class A + Inherits CSType1 + Implements I + + + Public Sub New( ByVal foo As Integer) + MyBase.New(1) +L: + Dim i As Integer = Len(New Integer) + i += 1 +#If DEBUG Then + Console.WriteLine(export.iefSupplied.command) +#End If + Const local? As Integer = Integer.MaxValue + Const local0? As Guid = New Guid(r.ToString()) + 'Inserted Compiling code + Dim r As Integer + Dim Varioblelocal? As Integer = Integer.MaxValue + Dim Varioblelocal0? As Guid = New Guid(r.ToString()) + Dim привет = local + Dim мир = local + Dim local3 = 0, local4 = 1 + Dim local5 = If(TryCast(Nothing, Action), Nothing) + Dim local6 = TypeOf local5 Is Action + Dim u = 1UI + Dim U_Renamed = 1UI + Dim hex As Long = &HBADC0DE, Hex_Renamed As Long = &HDEADBEEFL, l As Long = -1L, L_Renamed As Long = 1L + Dim ul As ULong = 1UL, Ul_Renamed As ULong = 1UL, uL_Renamed2 As ULong = 1UL, UL_Renamed3 As ULong = 1UL, lu As ULong = 1UL, Lu_Renamed1 As ULong = 1UL, lU_Renamed2 As ULong = 1UL, LU_Renamed3 As ULong = 1UL + Dim bool As Boolean + Dim [byte] As Byte + 'ChrW(&H0130), hexchar2 = ChrW(&HBAD) + 'ChrW(&H0066), hexchar = ChrW(&H0130), hexchar2 + '"c"c, \u0066 = ChrW(&H0066), hexchar + Dim [char] As Char = "c"c ', \u0066 + Dim [decimal] As Decimal = 1.44D + Dim [dynamic] As Object + Dim [double] As Double = m.PI + Dim float As Single + Dim int As Integer = If(local, -1) + Dim [long] As Long + Dim [object] As Object + Dim [sbyte] As SByte + Dim [short] As Short + Dim [string] As String = """/*" + Dim uint As UInteger + Dim [ulong] As ULong + Dim [ushort] As UShort + Dim dynamic1 = local5 + Dim add = 0 + Dim ascending = 0 + Dim descending = 0 + Dim From = 0 + Dim [get] = 0 + Dim [global] = 0 + Dim group = 0 + Dim into = 0 + Dim join = 0 + Dim [let] = 0 + Dim orderby = 0 + Dim [partial] = 0 + Dim remove = 0 + Dim [select] = 0 + Dim [set] = 0 + Dim value = 0 + Dim var = 0 + Dim where = 0 + Dim yield = 0 + If i > 0 Then + Return + ElseIf i = 0 Then + Throw New Exception() + End If + + Dim o1 = New MyObject() + Dim o2 = New MyObject(var) + Dim o3 = New MyObject With {.A = i} + Dim o4 = New MyObject(dynamic) With {.A = 0, .B = 0, .C = 0} + Dim o5 = New With {Key .A = 0} + Dim a() As Integer = {0, 1, 2, 3, 4, 5} + Select Case i + Case 1 + GoTo CaseLabel1 + Case 2 +CaseLabel1: + GoTo CaseLabel2 + Exit Select + Case Else +CaseLabel2: + Return + End Select + + Do While i < 10 + i += 1 + Loop + + Do + i += 1 + Loop While i < 10 + + For j As Integer = 0 To 99 + Console.WriteLine(j) + Next j + + 'Modified to include items + Dim items = {1, 2, 3, 4, 5, 6, 7, 8} + For Each i In items + If i = 7 Then + Return + Else + Continue For + End If + + Next i + + ' There is no equivalent to a 'checked' block in VB.NET + ' checked + i += 1 + 'Modified use of synclock functions for VB + Dim sText As String + Dim objLock As Object = New Object() + SyncLock objLock + sText = "Hello" + End SyncLock + + Using v = BeginScope() + Using a As New A() + Using BeginScope() + Return + End Using + + End Using + + End Using + + ' VB does not support iterators and has no equivalent to the C# 'yield' keyword: + 'yield Return Me.items(i) + ' VB does not support iterators and has no equivalent to the C# 'yield' keyword: + 'yield(break) + ' There is no equivalent to a 'fixed' block in VB.NET + 'Integer* p = Nothing + Try + Throw New Exception 'Nothing + Catch av As System.AccessViolationException + Throw av + Catch e1 As Exception + Throw + Finally + End Try + + Dim anonymous = New With {.a = 1, .B = 2, .c = 3} + Dim qry = From i1 In {1, 2, 3, 4, 5, 6} Where i1 < 5 Select New With {.id = i1} + Dim query = From c In customers Let d = c Where d IsNot Nothing Join c1 In customers On c1.GetHashCode() Equals c.GetHashCode() Group Join c1 In customers On c1.GetHashCode() Equals c.GetHashCode() Into e() Order By g.Count() Ascending Order By g.Key Descending Select New With {.Country = g.Key, .CustCount = g.Count()} + 'XML Literals + Dim x = + End Sub + + Protected Sub Finalize() + End Sub + + Private ReadOnly f1 As Integer + + ' There is no VB.NET equivalent to 'volatile': + + Private f2 As Integer + + + Public Sub Handler(ByVal value As Object) + End Sub + + Public Function m(Of T As {Class, New})(ByVal t1 As T) As Integer + MyBase.m(t1) + Return 1 + End Function + + Public Property P() As String + Get + Return "A" + End Get + + Set(ByVal value As String) + End Set + + End Property + + Public ReadOnly Property p2 As String + Get + End Get + + End Property + + Public Property p3 As String + + Default Public Property item(ByVal index As Integer) As Integer + Protected Get + End Get + + Set(ByVal value As Integer) + End Set + + End Property + + + Public Custom Event E1 As Action + ' This code will be run when AddHandler MyEvent, D1 is called + AddHandler(ByVal value As Action) + End AddHandler + + ' This code will be run when RemoveHandler MyEvent, D1 is called + RemoveHandler(ByVal value As Action) + End RemoveHandler + + + RaiseEvent() + End RaiseEvent + + End Event + + Public Shared Operator +(ByVal first, ByVal second) + Dim handler As System.Delegate = New [Delegate](AddressOf Me.Handler) + Return first.Add(second) + End Operator + + + Public Shared Operator IsTrue(ByVal a As A) As Boolean + Return True + End Operator + + Public Shared Operator IsFalse(ByVal a As A) As Boolean + Return False + End Operator + + Class c + + End Class + + Public Sub A(ByVal value As Integer) Implements I.A + End Sub + + Public Property Value As String Implements I.Value + Get + End Get + + Set(ByVal value As String) + End Set + + End Property + + End Class + + Public Structure S + Implements I + + Private f1 As Integer + + ' There is no VB.NET equivalent to 'volatile': + ' private volatile int f2; + + Private f2 As Integer + + Public Function m(Of T As {Structure, New})(ByVal s As T) As Integer + Return 1 + End Function + + Public Property P1() As String + Get + Dim value As Integer = 0 + Return "A" + End Get + + Set(ByVal value As String) + End Set + + End Property + + 'vb.net can't support abstract member variable + Public ReadOnly Property P2() As String + Get + End Get + + End Property + + Public Property p3 As String '//Auto Property + + Default Public Property item(ByVal index As Integer) As Integer + Get + End Get + + Friend Set(ByVal value As Integer) + End Set + + End Property + + Public Event E() + + Public Shared Operator +(ByVal first, ByVal second) + Return first.Add(second) + 'fixed Integer field(10) + End Operator + + Class c + + End Class + + Public Sub A(ByVal value As Integer) Implements I.A + End Sub + + Public Property Value As String Implements I.Value + Get + End Get + + Set(ByVal value As String) + End Set + + End Property + + End Structure + + Public Interface I + + Sub A(ByVal value As Integer) + + Property Value() As String + + End Interface + + + Public Enum E + A + B = A + C = 2 + A +#If DEBUG Then + D +#End If + End Enum + + Public Delegate Sub [Delegate](ByVal P As Object) + + Namespace Test + + Public Class Список + + Public Shared Function Power(ByVal number As Integer, ByVal exponent As Integer) As IEnumerable + Dim Список As New Список() + Список.Main() + Dim counter As Integer = 0 + Dim result As Integer = 0 + 'Do While ++counter++ < --exponent-- + ' result = result * number + +number + ++++number + ' ' VB does not support iterators and has no equivalent to the C# 'yield' keyword: + ' 'yield Return result + ' Loop + End Function + + Shared Sub Main() + For Each i As Integer In Power(2, 8) + Console.Write("{0} ", i) + Next i + + End Sub + + End Class + + End Namespace + +End Namespace + +Namespace ConsoleApplication1 + Namespace RecursiveGenericBaseType + + MustInherit Class A(Of T) + Inherits B(Of A(Of T), A(Of T)) + + Protected Overridable Function M() As A(Of T) + End Function + + Protected MustOverride Function N() As B(Of A(Of T), A(Of T)) + + Shared Function O() As B(Of A(Of T), A(Of T)) + End Function + + End Class + + Class B(Of T1, T2) + Inherits A(Of B(Of T1, T2)) + + Protected Overrides Function M() As A(Of T) + End Function + + Protected NotOverridable Overrides Function N() As B(Of A(Of T), A(Of T)) + End Function + + Shared Shadows Function O() As A(Of T) + End Function + + End Class + + End Namespace + +End Namespace + +Namespace Boo + + Public Class Bar(Of T As IComparable) + + Public f As T + + Public Class Foo(Of U) + Implements IEnumerator(Of T) + + Public Sub Method(Of K As {IList(Of V), IList(Of T), IList(Of U)}, V As IList(Of K))(ByVal k1 As K, ByVal t1 As T, ByVal u1 As U) + Dim a As A(Of Integer) + End Sub + + Public ReadOnly Property Current As T Implements System.Collections.Generic.IEnumerator(Of T).Current + Get + End Get + + End Property + + Public ReadOnly Property Current1 As Object Implements System.Collections.IEnumerator.Current + Get + End Get + + End Property + + Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext + End Function + + Public Sub Reset() Implements System.Collections.IEnumerator.Reset + End Sub + +#Region "IDisposable Support" + Private disposedValue As Boolean ' To detect redundant calls + + ' IDisposable + Protected Overridable Sub Dispose(ByVal disposing As Boolean) + If Not Me.disposedValue Then + If disposing Then + End If + + End If + + Me.disposedValue = True + End Sub + + Public Sub Dispose() Implements IDisposable.Dispose + Dispose(True) + GC.SuppressFinalize(Me) + End Sub + +#End Region + End Class + + End Class + +End Namespace + +Friend Class Test2 + + Private Sub Bar3() + Dim x = New Boo.Bar(Of Integer).Foo(Of Object)() + x.Method(Of String, String)(" ", 5, New Object()) + Dim q = From i In New Integer() {1, 2, 3, 4} Where i > 5 Select i + End Sub + + Public Shared Widening Operator CType(ByVal s As String) As Test2 + Return New Test2() + End Operator + + Public Shared Narrowing Operator CType(ByVal s As Integer) As Test2 + Return New Test2() + End Operator + + Public foo As Integer = 5 + + Private Sub Bar2() + foo = 6 + Me.foo = 5.GetType() + Dim t As Test2 = "sss" + End Sub + + Private Sub Blah() + Dim i As Integer = 5 + Dim j? As Integer = 6 + Dim e As Expression(Of Func(Of Integer)) = Function() i + End Sub + + Public Property FFoo() As Type + Get + Return GetType(System.Int32) + End Get + + Set(ByVal value As Type) + Dim t = GetType(System.Int32) + t.ToString() + t = value + End Set + + End Property + + Public Sub Constants() + Dim i As Integer = 1 + 2 + 3 + 5 + Dim s As Global.System.String = "a" & CStr("a") & "a" & "a" & "a" & "A" + End Sub + + Public Sub ConstructedType() + Dim i As List(Of Integer) = Nothing + Dim c As Integer = i.Count + End Sub + +End Class + +Namespace Comments.XmlComments.UndocumentedKeywords + + ''' + ''' Whatever + ''' + ''' + ''' // + ''' /* */ + ''' + ''' + ''' + ''' + ''' + ''' + ''' + ''' + ''' + ''' + Class c(Of T) + + Sub M(Of U)(ByVal T1 As T, ByVal U1 As U) + Dim intValue As Integer = 0 + intValue = intValue + 1 + Dim strValue As String = "hello" 's + Dim c As New [MyClass]() + Dim verbatimStr As String = "@ \\\\" 's + End Sub + + End Class + +End Namespace + +Friend Class TestClassXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 'Scen8 + +End Class + +Friend Class TestClass1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX22 'Scen9 + +End Class + +Friend Class yield + + ''INSTANT VB TODO TASK: There is no equivalent to the undocumented C# '__arglist' keyword in VB: + 'Private Sub Foo(Of U)(ByVal __arglist) + ' Dim c1 As C(Of U) = Nothing + ' c1.M(Of Integer)(5, Nothing) + ' Dim tr As TypedReference = __makeref(c1) + ' Dim t As Type = __reftype(tr) + ' Dim j As Integer = __refvalue(tr, Integer) + ' Params(a:=t, b:=t) + 'End Sub + Private Sub Params(ByRef a As Object, ByRef b As Object, ByVal ParamArray c() As Object) + End Sub + + 'Private Sub Params(Optional ByRef a As dynamic = 2, Optional ByRef c As dynamic = Nothing, ParamArray ByVal c()() As dynamic) + 'End Sub + Public Overrides Function ToString() As String + Return MyBase.ToString() + End Function + + Public Sub method() + Dim a?(4) As Integer '[] bug + ' YES [] + Dim var() As Integer = {1, 2, 3, 4, 5} ',; + Dim i As Integer = a(i) '[] + Dim f As New Foo(Of T)() '<> () + f.method() + i = i + i - i * i \ i Mod i And i Or i Xor i '+ - * / % & | ^ + Dim b As Boolean = True And False Or True Xor False '& | ^ + b = Not b '! + i = Not i '~i + b = i < i AndAlso i > i '< && > + Dim ii? As Integer = 5 '? bug + ' NO ? + Dim f1 As Integer = If(True, 1, 0) '? : + ' YES : + i += 1 '++ + i -= 1 '-- + b = True AndAlso False OrElse True '&& || + i = i << 5 '<< + i = i >> 5 '>> + b = i = i AndAlso i <> i AndAlso i <= i AndAlso i >= i '= == && != <= >= + i += 5.0 '+= + i -= i '-= + i *= i '*= + i \= i '/ + '= + i = i Mod i '%= + i = i And i '&= + i = i Or i '|= + i = i Xor i '^= + i <<= i '<<= + i >>= i '>>= + Dim s As Object = Function(x) x + 1 '=> + ' There is no equivalent to an 'unsafe' block in VB.NET + ' unsafe + ' Point* p = &point '* & + ' p->x = 10 '-> + Dim p As Point + p.X = 10 + p.Y = 12 + Dim p2 As New Point With {.X = 10, .Y = 12} + Dim br As IO.BinaryReader = Nothing + End Sub + + Friend Structure Point + + Public X As Integer + + Public Y As Integer + + End Structure + +End Class + +'Extension Method +Module Module1 + + Function FooExtension(ByVal x As String) As String + Return x & "test" + End Function + + + Function FooExtension(ByVal x As String, ByVal y As Integer) As String + 'With Implicit Line Continuation + Return x & "test2" + End Function + + Sub Foo() + 'Collections + Dim i As New List(Of String) From {"test", "item"} + Dim i1 As New Dictionary(Of Integer, String) From {{1, "test"}, {2, "item"}} + 'Arrays + Dim ia1 = {1, 2, 3, 4, 5} + Dim la2 = {1, 2L, 3, 4S, 5} + Console.Write(GetXmlNamespace(ns)) + Dim ia3 As Integer() = {1, 2, 3, 4, 5} + Dim ia4() As Integer = {1, 2, 3, 4, 5} + Dim ia5 = New Integer() {1, 2, 3, 4, 5} + Dim ia6 = {{1, 2}, {3, 4}, {5, 6}} '2d array + Dim ia7 = {({1}), ({3, 4}), ({5, 6, 2})} 'jagged array + 'Standalone + If {1, 2, 3}.Count = 2 Then + ElseIf {1, 2, 3}.Count = 3 Then + Else + End If + + End Sub + +End Module + +#Region "Events" +Public Delegate Sub MyDelegate(ByVal message As String) +Class MyClass1 + + Custom Event MyEvent As MyDelegate + ' This code will be run when AddHandler MyEvent, D1 + ' is called + AddHandler(ByVal value As MyDelegate) + Console.WriteLine("Adding Handler for MyEvent") + MyEventHandler = value + End AddHandler + + ' This code will be run when RemoveHandler MyEvent, D1 + ' is called + RemoveHandler(ByVal value As MyDelegate) + Console.WriteLine("Removing Handler for MyEvent") + MyEventHandler = Nothing + End RemoveHandler + + ' This code will be run when RaiseEvent MyEvent(string) + ' is called + RaiseEvent(ByVal message As String) + If Not MyEventHandler Is Nothing Then + MyEventHandler.Invoke(message) + Else + Console.WriteLine("No Handler for Raised MyEvent") + End If + + End RaiseEvent + + End Event + + Public MyEventHandler As MyDelegate + + Public Sub Raise_Event() + RaiseEvent MyEvent("MyEvent Was Raised") + End Sub + +End Class + +Module DelegateModule + Dim Var1 As MyClass1 + Dim D1 As MyDelegate + Sub EventsMain() + Var1 = New MyClass1 + D1 = New MyDelegate(AddressOf MyHandler) + AddHandler Var1.MyEvent, D1 + Var1.Raise_Event() + RemoveHandler Var1.MyEvent, D1 + End Sub + + Sub MyHandler(ByVal message As String) + Console.WriteLine("Event Handled: " & message) + End Sub + +End Module + +#End Region +#Region "Linq" +Module LINQQueries + Sub Join() + Dim categories() = {"Beverages", "Condiments", "Vegetables", "Dairy Products", "Seafood"} + Dim productList = {New With {.category = "Condiments", .name = "Ketchup"}, New With {.category = "Seafood", .name = "Code"}} + Dim query = From c In categories Group Join p In productList On c Equals p.category Into Group From p In Group Select Category = c, p.name + For Each v In query + Console.WriteLine(v.name + ": " + v.Category) + Next + + End Sub + +End Module + +#End Region +#Region "Lambda's" +Module Lambdas + Dim l1 = Sub() + Console.WriteLine("Sub Statement") + End Sub + + Dim L2 = Sub() Console.WriteLine("Sub Statement 2") + Dim L3 = Function(x As Integer) x Mod 2 + Dim L4 = Function(y As Integer) As Boolean + If y * 2 < 10 Then + Return True + Else + Return False + End If + + End Function +End Module + +#End Region +#Region "Co Contra Variance" +Public Class Cheetah + +End Class + +Public Class Animals + +End Class + +Public Interface IVariance(Of In T) + + Sub Foo(ByVal a As T) + + Property InterProperty() As IVariance(Of Cheetah) + + Property InterProperty2() As IVariance(Of Animals) + +End Interface + +Delegate Sub Func(Of In T)(ByVal a As T) +Public Delegate Function Func2(Of Out T)() As T +Public Interface IVariance2(Of Out T) + + Function Foo() As T + +End Interface + +Public Class Variance2(Of T As New) : Implements IVariance2(Of T) + + Dim type As IVariance2(Of Animals) + + Public Function Foo() As T Implements IVariance2(Of T).Foo + Return New T + End Function + + Function Foo(ByVal arg As IVariance2(Of T)) As String + Return arg.GetType.ToString + End Function + + Function Goo(ByVal arg As Func2(Of T)) As String + Return arg.Invoke().GetType.ToString + End Function + +End Class + +#End Region +Module Mod1Orcas + Dim AT1 = New With {Key .prop1 = 1} + Dim AT2 = New With {.prop1 = 7} + Dim b_false As Boolean = False + Dim n_false = False + Dim i = If(b_false And n_false, 1, 2) + Dim s1 = <%= If(Nothing, Nothing) %> + Delegate Sub delfoo() + Delegate Sub delfoo1(ByVal sender As Object, ByVal e As System.EventArgs) + Sub Foo() + End Sub + + Sub Method1(ByVal sender As Object, ByVal e As System.EventArgs) + End Sub + + Sub Method1a() + End Sub + + Sub AssignDelegate() + Dim d As delfoo = AddressOf Foo + d.Invoke() + Dim d1_1 As delfoo1 = AddressOf Method1 + Dim d1_1a As delfoo1 = AddressOf Method1a 'Relaxed Delegate + 'Nullable + Dim Value1a As Integer? = 10 + Dim Value1b As Integer = 1 + Dim Value1c? As Integer = 1 + Dim Value1c? As Integer? = 1 + Dim TestReturnValue = Value1a * Value1b + If Value1a / Value1b > 0 Then + End If + + Dim sNone = "None" + Dim SSystemOnly = "SystemOnly" + Dim XMLLiteral =
>> + Imports System + + Imports System + Imports System.Collections + public Module {Identifier}End Module Module_public class {Identifier}End ClassClass_public class {Identifier}End ClassStruct_ = Microsoft.VisualBasic.FileSystem.Dir(".") ]]> = 1 ]]> as string = "2" ]]>
+ Dim x = as string = "2" ]]> + Dim y = : Call () : Dim x = + End Sub + +End Module + +Class Customer + + Public Property name As String = "Default" + + Public AGe As Integer + + Public Postion As String + + Public Level As Integer = 0 + + Public Property age2 As Integer + +End Class + +Class Foo + + Structure Bar + + Dim x As Integer + + Sub LoopingMethod() + For i = 1 To 20 Step 1 + Next i + + For Each a In {1, 2, 3, 4} + Next + + Dim icount As Integer + Do While icount <= 10 + icount += 1 + Loop + + icount = 0 + While icount <= 100 + icount += 1 + End While + + icount = 0 + Do Until icount >= 10 + icount += 2 + Loop + + End Sub + + End Structure + +End Class + +Class FooGen(Of t) + + Structure BarGen(Of u) + + Dim x As t + + Dim z As u + + Sub SelectionMethods() + Dim icount As Integer = 1L + If icount = 1 Then + ElseIf icount > 1 Then + Else + End If + + Select Case icount + Case 1 + Case 2, 3 + Case Is > 3 + Case Else + End Select + + End Sub + + Sub Operators() + Dim a As Boolean = True + Dim b As Boolean = False + If a And b Then + End If + + If a Or b Then + End If + + If Not a And b Then + End If + + If a = b AndAlso b = True Then + End If + + If a = b OrElse b = False Then + End If + + If(a Or b) OrElse b = True Then + End If + + End Sub + + Sub Method1() + Dim x As New Customer With {.name = "Test", .AGe = 30, .Level = 1, .Postion = "SDET"} + Dim x2 As New Customer With {.name = "Test", .AGe = 30, .Level = 1, .Postion = "SDET", .age2 =.AGe} + End Sub + + End Structure + +End Class + +Public Class Bar + +End Class + +Public Class ClsPPMTest003 + + Partial Private Sub Foo3() + End Sub + +End Class + +Partial Public Class ClsPPMTest003 + + Private Sub Foo3() + End Sub + + Public Sub CallFooFromClass() + Me.Foo3() + Dim x1 As New Foo + Dim y1 As New Bar + If x1 Is y1 Then + Else + Console.WriteLine("Expected Result Occured") + End If + + If x1 IsNot y1 Then + Else + Console.WriteLine("Expected Result Occured") + End If + + End Sub + +End Class + diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/UnitTest1.vb b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/UnitTest1.vb new file mode 100644 index 000000000..0b9e9b0c1 --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/UnitTest1.vb @@ -0,0 +1,1139 @@ +Option Strict Off + +Imports Microsoft.CodeAnalysis +Imports Xunit +Imports CS = Microsoft.CodeAnalysis.CSharp +Imports VB = Microsoft.CodeAnalysis.VisualBasic +Imports VisualBasicToCSharpConverter + +Namespace VisualBasicToCSharpConverter.UnitTests.Converting + + Public Class VisualBasicToCSharpConverterTests + + ' File-level or project-level code snippets would be nice :). + ' + Sub TestTemplate() + + AssertConversion( + + +, + + + + ) + + End Sub + + + Sub TestConvertSimpleTypes() + + AssertConversion( + +Class C + +End Class + +Interface I + +End Interface + +Structure S + +End Structure + +Delegate Sub D1() + +Delegate Sub D2(p1 As T1) + +Delegate Function D3(p1 As T1, ByRef p2 As T2) As ReturnType + +Enum E + A + B + C +End Enum +, + +class C +{ +} + +interface I +{ +} + +struct S +{ +} + +delegate void D1(); + +delegate void D2(T1 p1); + +delegate ReturnType D3(T1 p1, ref T2 p2); + +enum E +{ + A, + B, + C +} + + ) + + End Sub + + + Sub TestConvertLoadedSimpleTypes() + + AssertConversion( + +' This is a comment. +<System.Serializable()> +Friend MustInherit Class C(Of T As {New, IDisposable, Foo}, U As T) + Inherits Object + Implements IDisposable, IA, IB + Implements IC, ID + +End Class + +Public Delegate Sub Action() +Private Delegate Sub Action(Of In T)(arg0 As T) +Delegate Function Func(Of Out T)() As T + +' Trivia. +<Global.System.Flags> +Public Enum E As UShort + ' Trivia. + A = 0 + B = 1 >> 0 ' Trivia. + C = 1 >> 1 + <DisplayName("B and C")> + D = B And C +End Enum +, + +// This is a comment. +[System.Serializable()] +internal abstract class C<T, U> : object, IDisposable, IA, IB, IC, ID where T : IDisposable, Foo, new() where U : T +{ +} + +public delegate void Action(); +private delegate void Action<in T>(T arg0); +delegate T Func<out T>(); + +// Trivia. +[global::System.Flags] +public enum E : ushort +{ + // Trivia. + A = 0, + B = 1 >> 0 // Trivia. +, + C = 1 >> 1, + [DisplayName("B and C")] + D = B & C +} + + ) + + End Sub + + + Sub TestConvertFieldsAndLocalVariables() + 'Array modifiers aren't supported yet. + 'Private F5(), F6?(), F7 As T3 + + AssertConversion( + +Class C + + Private F1 As T1, F2, F3 As T2, F4?, F5 As T3 + + Sub M() + Dim l1 As T1, l2, l3 As T2, l4?, l5 As T3 + End Sub + +End Class +, + +class C +{ + private T1 F1; + private T2 F2; + private T2 F3; + private T3? F4; + private T3 F5; + + void M() + { + T1 l1; + T2 l2; + T2 l3; + T3? l4; + T3 l5; + } +} + + ) + + End Sub + + + Sub TestConvertTypeCharactersAndVariableModifiers() + + AssertConversion( + +Class C + Sub M() + Dim i%, o, s$ + Dim arr%(), arr2%?() + Dim arr3() As Integer, arr4 As Integer?() + End Sub +End Class +, + +class C +{ + void M() + { + int i; + dynamic o; + string s; + int[] arr; + int?[] arr2; + int[] arr3; + int?[] arr4; + } +} + + ) + + End Sub + + + Sub TestConvertAsNewAndInitializers() + + AssertConversion( + +Class C + Sub M() + Dim obj As New C + Dim a, b, c As New C("Hello") + Dim d = New C() With {.Text = "Hello"} + Dim e As New C() With {.Text = "Goodbye"} + Dim f = New List(Of Integer) From { 1, 2, 3 } + Dim g As New List(Of Integer) From { 1, 2, 3 } + End Sub +End Class +, + +class C +{ + void M() + { + var obj = new C(); + var a = new C("Hello"); + var b = new C("Hello"); + var c = new C("Hello"); + var d = new C() { Text = "Hello" }; + var e = new C() { Text = "Goodbye" }; + var f = new List<int>() { 1, 2, 3 }; + var g = new List<int>() { 1, 2, 3 }; + } +} + + ) + + End Sub + + + Sub TestConvertInterfaceMembers() + + AssertConversion( + +Interface IA + Inherits IB, IC + Inherits ID + + Sub M() + + Function N() As String + + ReadOnly Property P1 As Char + + Property P2 As Object + +End Interface +, + +interface IA : IB, IC, ID +{ + + void M(); + + string N(); + + char P1 { get; } + + object P2 { get; set; } +} + + ) + + End Sub + + + Sub TestConvertArrayDeclarations() + + AssertConversion( + +Class C + Sub M() + Dim a() As Integer + Dim b(1024 - 1) As Byte + Dim c(1023) As Byte + Dim d(0 To -1) As Object + Dim e = New String() {} + Dim f = New String(10, 10) {} + Dim g(5)(,) As Double + Dim h = New Single(0 To 6, 0 To 8)(,,)(,)() {} + Dim importantDates = new Date() {Date.MinValue, Date.Now, Date.MaxValue} + Dim nulls() As Object = {Nothing, Nothing, Nothing} + End Sub +End Class +, + +class C +{ + void M() + { + int[] a; + byte[] b = new byte[1024]; + byte[] c = new byte[1024]; + object[] d = new object[0]; + var e = new string[] {}; + var f = new string[11, 11]; + double[][,] g = new double[6][,]; + var h = new float[7, 9][,,][,][]; + var importantDates = new global::System.DateTime[] {global::System.DateTime.MinValue, global::System.DateTime.Now, global::System.DateTime.MaxValue}; + object[] nulls = {null, null, null}; + } +} + + ) + + End Sub + + + Sub TestConvertAbstractMembers() + + AssertConversion( + +MustInherit Class C + MustOverride Function M1() As Integer + + Public MustOverride Readonly Property P1 As Decimal + + Protected Overridable Sub M2() + + End Sub +End Class +, + +abstract class C +{ + + abstract int M1(); + + public abstract decimal P1 { get; } + + protected virtual void M2() + { + } +} + + ) + + End Sub + + + Sub TestConvertCompilationUnit() + + AssertConversion( + +Imports System +Imports System.Collections.Generics +Imports System.Windows, System.Windows.Forms + +<Assembly: A(), Module: B()> +<Assembly: C> +, + +using System; +using System.Collections.Generics; +using System.Windows; +using System.Windows.Forms; + +[assembly: A()] +[module: B()] +[assembly: C] + + ) + + End Sub + + + Sub TestConvertMembers() + + AssertConversion( + +Class C + + Sub New(p1 As T1) + Me.New() + End Sub + + ' Trivia. + Property P1 As T1 + + Protected Property P2 As T2 + Get + Return Nothing + End Get + Set(value As T2) + + End Set + End Property + + ' Trivia. + Private ReadOnly Property P3 As T3 + Get + Return Nothing + End Get + End Property + + Sub M1() + MyBase.M1() + End Sub + + Sub M2(ByRef p1 As T1, Optional p2 As T2 = 1) + + End Sub + + Function M3(Of T As Structure)() As Date + + End Function + + Public Event Click As EventHandler + + Public Shared Operator +(a As C, b As C) As C + Return "Empty" + End Operator + + Public Shared Narrowing Operator CType(value As String) As C + + End Operator +End Class +, + +class C +{ + C(T1 p1) : this() + { + } + + // Trivia. + T1 P1 { get; set; } + + protected T2 P2 + { + get + { + return null; + } + set + { + } + } + + // Trivia. + private T3 P3 + { + get + { + return null; + } + } + + void M1() + { + base.M1(); + } + + void M2(ref T1 p1, T2 p2 = 1) + { + } + + global::System.DateTime M3<T>() where T : struct + { + } + + public event EventHandler Click; + + public static C operator +(C a, C b) + { + return "Empty"; + } + + public static explicit operator C(string value) + { + } +} + + ) + + End Sub + + + Sub TestConvertNamespace() + + ' TODO: Test RootNamespace. + AssertConversion( + +Namespace A + Class C + + End Class + + Namespace B + Namespace D.E.F + + End Namespace + End Namespace +End Namespace + +Namespace A.B.D + +End Namespace + +Namespace Global.G + +End Namespace +, + +namespace A +{ + class C + { + } + + namespace B + { + namespace D.E.F + { + } + } +} + +namespace A.B.D +{ +} + +namespace G +{ +} + + ) + + End Sub + + + Sub TestConvertTrySyncUsing() + + AssertConversion( + +Class C + Sub M() + ' Try-Catch-All. + Try + Connection.Open() + Catch + Throw + End Try + + ' Try-Finally. + Try + Connection.Open() + + Connection.Close() + Finally + If Connection IsNot Nothing Then Connection.Close() + End Try + + ' Try-Catch. + Try + Socket.Send(Data) + Catch ex As InvalidCastException + WriteLine(ex) + Catch ex As SocketException + Throw New Exception(ex) + Catch ex As Exception + WriteLine(ex) + End Try + + ' Try-Catch-Finally. + Try + Throw New Exception() + Catch ex As Exception + + Finally + WriteLine("Done!") + End Try + + SyncLock resource + + End SyncLock + + Using resource + + End Using + + Using connection As New SqlConnection(ConnectionString) + + End Using + + Using resource = GetResource() + + End Using + + Using connection = CreateConnection(), + command = connection.CreateCommand(), + reader = command.ExecuteReader() + + End Using + End Sub +End Class +, + +class C +{ + void M() + { + // Try-Catch-All. + try + { + Connection.Open(); + } + catch + { + throw; + } + + // Try-Finally. + try + { + Connection.Open(); + + Connection.Close(); + } + finally + { + if (Connection != null) { Connection.Close(); } + } + + // Try-Catch. + try + { + Socket.Send(Data); + } + catch (InvalidCastException ex) + { + WriteLine(ex); + } + catch (SocketException ex) + { + throw new Exception(ex); + } + catch (Exception ex) + { + WriteLine(ex); + } + + // Try-Catch-Finally. + try + { + throw new Exception(); + } + catch (Exception ex) + { + } + finally + { + WriteLine("Done!"); + } + + lock (resource) + { + } + + using (resource) + { + } + + using (var connection = new SqlConnection(ConnectionString)) + { + } + + using (var resource = GetResource()) + { + } + + using (var connection = CreateConnection()) + using (var command = connection.CreateCommand()) + using (var reader = command.ExecuteReader()) + { + + } + } +} + + ) + + End Sub + + + Sub TestConvertIf() + + AssertConversion( + +Class C + Sub M() + If True Then Return + If False Then Return : Return : Else Return + If True + + ElseIf 1 > 2 Then + + ElseIf String.IsNullOrEmpty(String.Empty) + Console.Beep() + Else + Return + End If + End Sub +End Class +, + +class C +{ + void M() + { + if (true) + { + return; + } + + if (false) + { + return; + return; + } + else + { + return; + } + + if (true) + { + } + else if (1 > 2) + { + } + else if (string.IsNullOrEmpty(string.Empty)) + { + Console.Beep(); + } + else + { + return; + } + } +} + + ) + + End Sub + + + Sub TestConvertSelectCase() + + AssertConversion( + +Class C + Sub M() + Select Case kind + Case SyntaxKind.FieldDeclaration, SyntaxKind.LocalDeclaration + Return + Case SyntaxKind.UsingBlock + Visit(node) + Case Else + Throw New NotSupportedException() + End Select + End Sub +End Class +, + +class C +{ + void M() + { + switch (kind) + { + case SyntaxKind.FieldDeclaration: + case SyntaxKind.LocalDeclaration: + return; + break; + case SyntaxKind.UsingBlock: + Visit(node); + break; + default: + throw new NotSupportedException(); + } + } +} + + ) + + End Sub + + + Sub TestConvertCasts() + + AssertConversion( + +Class C +Friend Sub M(obj As Object) + Dim casts = {CType(obj, Integer).ToString(), DirectCast(obj, String), TryCast(obj, C).M(obj)} + + Dim values = {CByte(obj), CUShort(obj), CUInt(obj), CULng(obj), + CSByte(obj), CShort(obj), CInt(obj), CLng(obj), + CBool(obj), CDate(obj), CObj(obj), + CChar(obj), CStr(obj), + CSng(obj), CDbl(obj), CDec(obj)} + End Sub +End Class +, + +class C +{ + internal void M(object obj) + { + var casts = new[] {((int)obj).ToString(), ((string)obj), (obj as C).M(obj)}; + + var values = new[] {((byte)obj), ((ushort)obj), ((uint)obj), ((ulong)obj), + ((sbyte)obj), ((short)obj), ((int)obj), ((long)obj), + ((bool)obj), ((global::System.DateTime)obj), ((object)obj), + ((char)obj), ((string)obj), + ((float)obj), ((double)obj), ((decimal)obj)}; + } +} + + ) + + End Sub + + + Sub TestConvertLoops() + + AssertConversion( + +Class C + Function M() As Integer + While True + Console.Beep() + End While + + Do : Loop + + Do While enumerator.MoveNext() + Loop + + Do Until stream.EndOfFile + Loop + + Do + Loop While Peek() IsNot Nothing + + Do + Loop Until Peek() = -1 + + For Each control In Controls + Next + + For Each c As Control in Controls + Next + + For i = 1 To 10 + Next + + For i = 0 To 100 Step 10 + Next + + For i As Integer = 0 To arr.Length - 1 + Next + + For i = arr.Length - 1 To 0 Step -1 + Next + + For i = 1 To Sheets.Count + Next + End Function +End Class +, + +class C +{ + int M() + { + while (true) + { + Console.Beep(); + } + + while (true) + { + } + + while (enumerator.MoveNext()) + { + } + + while (!(stream.EndOfFile)) + { + } + + do + { + } + while (Peek() != null); + + do + { + } + while (!(Peek() == -1)); + + foreach (var control in Controls) + { + } + + foreach (Control c in Controls) + { + } + + for (var i = 1; i <= 10; i++) + { + } + + for (var i = 0; i <= 100; i += 10) + { + } + + for (int i = 0; i < arr.Length; i++) + { + } + + for (var i = arr.Length - 1; i >= 0; i--) + { + } + + for (var i = 1; i <= Sheets.Count; i++) + { + } + } +} + + ) + + End Sub + + + Sub TestConvertLinq() + + AssertConversion( + +Class C + Sub M() + + Dim q = From item In Items + + Dim q = From item In Items Distinct + + Dim q = From item In Items Where item.IsSelected AndAlso True + + Dim q = From item In Items Where item.IsSelected AndAlso True Select item.ProductId, item.UnitPrice + + Dim q = From item In Items Order By item.UnitPrice + + Dim q = From item In Items Join product in Products On item.ProductId Equals product.Id + + End Sub +End Class +, + +class C +{ + void M() + { + var q = from item in Items select item; + + var q = (from item in Items select item).Distinct(); + + var q = from item in Items where item.IsSelected && true select item; + + var q = from item in Items where item.IsSelected && true select new { item.ProductId, item.UnitPrice }; + + var q = from item in Items orderby item.UnitPrice select item; + + var q = from item in Items join product in Products on item.ProductId equals product.Id select new { item, product }; + } +} + + ) + + End Sub + + + Public Sub TestAsyncModifier() + + AssertConversion( + +Async Sub M() +End Sub + +Async Function N() As Task +End Function + +Async Function O() As Task(Of Integer) +End Function +, + +async void M() +{ +} + +async Task N() +{ +} + +async Task<int> O() +{ +} + + ) + + End Sub + + + Public Sub TestAwaitExpression() + + AssertConversion( + +Async Sub Button1_Click(sender As Object, e As EventArgs) + ResultsTextBox.Text = Await httpClient.DownloadStringTaskAsync("http://somewhere.com/") +End Sub +, + +async void Button1_Click(object sender, EventArgs e) +{ + ResultsTextBox.Text = await httpClient.DownloadStringTaskAsync("http://somewhere.com/"); +} + + ) + + End Sub + + + Public Sub TestAwaitStatement() + + AssertConversion( + +Async Sub Button1_Click(sender As Object, e As EventArgs) + Await BeepAsync() +End Sub +, + +async void Button1_Click(object sender, EventArgs e) +{ + await BeepAsync(); +} + + ) + + End Sub + + + Public Sub TestAsyncLambdas() + + AssertConversion( + +Sub M() + Task.Run(Async Function() + End Function) + Task.Run(Async Function() Await NAsync()) + Task.Run(Async Sub() Await NAsync()) + Task.Run(Async Sub() + End Sub) +End Sub +, + +void M() +{ + Task.Run(async () => + { + } + + ); + Task.Run(async () => await NAsync()); + Task.Run(async () => + { + await NAsync(); + } + + ); + Task.Run(async () => + { + } + + ); +} + + ) + + End Sub + + + Sub TestConvertUnsupportedDoesntThrow() + Dim actual = Converter.ConvertTree(VB.SyntaxFactory.ParseSyntaxTree(My.Resources.VBAllInOne)) + End Sub + + Sub AssertConversion(ByVal source As String, ByVal expected As String) + + Dim tree = VB.SyntaxFactory.ParseSyntaxTree(source) + + Normalize(expected) + + Dim actual = Converter.ConvertTree(tree).ToFullString() + + Assert.Equal(expected, actual) + + End Sub + + Private Sub Normalize(ByRef value As String) + value = CS.SyntaxFactory.ParseCompilationUnit(value).NormalizeWhitespace().ToFullString() + End Sub + End Class + +End Namespace + diff --git a/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/VisualBasicToCSharpConverter.UnitTests.vbproj b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/VisualBasicToCSharpConverter.UnitTests.vbproj new file mode 100644 index 000000000..637835a51 --- /dev/null +++ b/samples/VisualBasic/VisualBasicToCSharpConverter/VisualBasicToCSharpConverter.Test/VisualBasicToCSharpConverter.UnitTests.vbproj @@ -0,0 +1,33 @@ + + + + netcoreapp2.0 + false + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + VbMyResourcesResXFileCodeGenerator + My.Resources + Resources.Designer.vb + + + + diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 96b4ee100..a6bde5ece 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,5 +1,44 @@ - - - - - \ No newline at end of file + + + + + + strict + + + + + + + + 16 + + + + + + + 9 + 9999 + enable + + + + + + + false + + + + $(CopyrightMicrosoft) + MIT + + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index dca3e5089..c19f17c24 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -1,4 +1,4 @@ - + - + \ No newline at end of file diff --git a/src/Microsoft.CodeAnalysis.Testing/Directory.Build.props b/src/Microsoft.CodeAnalysis.Testing/Directory.Build.props new file mode 100644 index 000000000..8b5745550 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Directory.Build.props @@ -0,0 +1,101 @@ + + + + false + + + false + + + + + + + netcoreapp3.1;netstandard1.5;netstandard2.0;net452;net46;net472 + + + netcoreapp3.1;netstandard2.0;net472 + + + + $(CopyrightMicrosoft) + MIT + + + + + + 15.5 + + + + + + 8 + + + + + enable + + + + + + + portable-net45+win8 + + + + + $(NoWarn);NU1701;NU3005;NU5125 + + + + + false + embedded + true + true + + + + + true + $(MSBuildThisFileDirectory)..\..\RoslynSDK.ruleset + + + + + + + + + + + + + + + + + + + $(NugetPackagePrefix) + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Directory.Build.targets b/src/Microsoft.CodeAnalysis.Testing/Directory.Build.targets new file mode 100644 index 000000000..eeb2cf07d --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Directory.Build.targets @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/AnalyzerInfo.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/AnalyzerInfo.cs new file mode 100644 index 000000000..525e6cb35 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/AnalyzerInfo.cs @@ -0,0 +1,171 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Threading; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.Testing +{ + internal static class AnalyzerInfo + { + /// + /// The constructor. + /// + private static readonly ConstructorInfo AttributeBaseClassCtor = typeof(Attribute).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).Single(ctor => ctor.GetParameters().Length == 0); + + /// + /// The constructor. + /// + private static readonly ConstructorInfo AttributeUsageCtor = typeof(AttributeUsageAttribute).GetConstructor(new Type[] { typeof(AttributeTargets) })!; + + /// + /// The property. + /// + private static readonly PropertyInfo AttributeUsageAllowMultipleProperty = typeof(AttributeUsageAttribute).GetProperty(nameof(AttributeUsageAttribute.AllowMultiple))!; + + private static readonly object s_codeGenerationLock = new object(); + private static Type? s_generatedAnalysisContextType; + + public static bool HasConfiguredGeneratedCodeAnalysis(DiagnosticAnalyzer analyzer) + { + var context = CreateAnalysisContext(); + analyzer.Initialize(context); + return context.ConfiguresGeneratedCode; + } + + private static CustomAnalysisContext CreateAnalysisContext() + { + if (s_generatedAnalysisContextType is null) + { + lock (s_codeGenerationLock) + { + if (s_generatedAnalysisContextType is null) + { + s_generatedAnalysisContextType = GenerateAnalysisContextType(); + } + } + } + + return (CustomAnalysisContext)Activator.CreateInstance(s_generatedAnalysisContextType)!; + } + + private static Type GenerateAnalysisContextType() + { + Debug.Assert(Monitor.IsEntered(s_codeGenerationLock), "Assertion failed: Monitor.IsEntered(s_codeGenerationLock)"); + + var moduleBuilder = CreateModuleBuilder(); + var typeBuilder = moduleBuilder.DefineType("CustomAnalysisContextImpl", TypeAttributes.Public, typeof(CustomAnalysisContext)); + foreach (var method in typeof(AnalysisContext).GetTypeInfo().DeclaredMethods) + { + if (!method.IsVirtual && !method.IsAbstract) + { + continue; + } + + if (method.ReturnType != typeof(void)) + { + continue; + } + + var accessAttributes = method.Attributes & MethodAttributes.MemberAccessMask; + var methodBuilder = typeBuilder.DefineMethod(method.Name, accessAttributes | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.Virtual, method.ReturnType, method.GetParameters().Select(parameter => parameter.ParameterType).ToArray()); + if (method.IsGenericMethod) + { + var genericParameterBuilders = methodBuilder.DefineGenericParameters(method.GetGenericArguments().Select(type => type.Name).ToArray()); + for (var i = 0; i < genericParameterBuilders.Length; i++) + { + var parameterBuilder = genericParameterBuilders[i]; + parameterBuilder.SetBaseTypeConstraint(method.GetGenericArguments()[i].GetTypeInfo().BaseType); + parameterBuilder.SetInterfaceConstraints(method.GetGenericArguments()[i].GetTypeInfo().ImplementedInterfaces.ToArray()); + } + } + + var generator = methodBuilder.GetILGenerator(); + + if (method.Name == "ConfigureGeneratedCodeAnalysis") + { + var generatedCodeField = typeof(CustomAnalysisContext).GetTypeInfo().DeclaredFields.Single(field => field.Name == nameof(CustomAnalysisContext.ConfiguresGeneratedCode)); + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldc_I4_1); + generator.Emit(OpCodes.Stfld, generatedCodeField); + } + + generator.Emit(OpCodes.Ret); + } + + return typeBuilder.CreateTypeInfo()!.AsType(); + } + + private static ModuleBuilder CreateModuleBuilder() + { + var assemblyBuilder = CreateAssemblyBuilder(); + var moduleBuilder = assemblyBuilder.DefineDynamicModule("codeAnalysisProxies"); + + SkipVisibilityChecksFor(assemblyBuilder, moduleBuilder, typeof(CustomAnalysisContext)); + + return moduleBuilder; + } + + private static AssemblyBuilder CreateAssemblyBuilder() + { + var assemblyName = new AssemblyName($"codeAnalysisProxies_{Guid.NewGuid()}"); + return AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndCollect); + } + + private static void SkipVisibilityChecksFor(AssemblyBuilder assemblyBuilder, ModuleBuilder moduleBuilder, Type type) + { + var attributeBuilder = new CustomAttributeBuilder(GetMagicAttributeCtor(moduleBuilder), new object[] { type.GetTypeInfo().Assembly.GetName().Name! }); + assemblyBuilder.SetCustomAttribute(attributeBuilder); + } + + private static ConstructorInfo GetMagicAttributeCtor(ModuleBuilder moduleBuilder) + { + var magicAttribute = EmitMagicAttribute(moduleBuilder); + return magicAttribute.GetConstructor(new Type[] { typeof(string) })!; + } + + private static System.Reflection.TypeInfo EmitMagicAttribute(ModuleBuilder moduleBuilder) + { + var tb = moduleBuilder.DefineType( + "System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute", + TypeAttributes.NotPublic, + typeof(Attribute)); + + var attributeUsage = new CustomAttributeBuilder( + AttributeUsageCtor, + new object[] { AttributeTargets.Assembly }, + new PropertyInfo[] { AttributeUsageAllowMultipleProperty }, + new object[] { false }); + tb.SetCustomAttribute(attributeUsage); + + var cb = tb.DefineConstructor( + MethodAttributes.Public | + MethodAttributes.HideBySig | + MethodAttributes.SpecialName | + MethodAttributes.RTSpecialName, + CallingConventions.Standard, + new Type[] { typeof(string) }); + cb.DefineParameter(1, ParameterAttributes.None, "assemblyName"); + + var il = cb.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Call, AttributeBaseClassCtor); + il.Emit(OpCodes.Ret); + + return tb.CreateTypeInfo()!; + } + + internal abstract class CustomAnalysisContext : AnalysisContext + { + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Set via reflection.")] + public bool ConfiguresGeneratedCode; + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/AnalyzerTest`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/AnalyzerTest`1.cs new file mode 100644 index 000000000..6410ab7e1 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/AnalyzerTest`1.cs @@ -0,0 +1,1452 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Testing.Model; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Composition; +using IComparer = System.Collections.IComparer; + +namespace Microsoft.CodeAnalysis.Testing +{ + public abstract class AnalyzerTest + where TVerifier : IVerifier, new() + { + private static readonly Lazy ExportProviderFactory; + + static AnalyzerTest() + { + 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).WithDocumentTextDifferencingService(); + + var configuration = CompositionConfiguration.Create(catalog); + var runtimeComposition = RuntimeComposition.CreateRuntimeComposition(configuration); + return runtimeComposition.CreateExportProviderFactory(); + }, + LazyThreadSafetyMode.ExecutionAndPublication); + } + + /// + /// Gets the default verifier for the test. + /// + protected static TVerifier Verify { get; } = new TVerifier(); + + /// + /// Gets the prefix to apply to source files added without an explicit name. + /// + protected virtual string DefaultFilePathPrefix { get; } = "/0/Test"; + + /// + /// Gets the name of the default project created for testing. + /// + protected virtual string DefaultTestProjectName { get; } = "TestProject"; + + /// + /// Gets the default full name of the first source file added for a test. + /// + protected virtual string DefaultFilePath => DefaultFilePathPrefix + 0 + "." + DefaultFileExt; + + /// + /// Gets the default file extension to use for files added to the test without an explicit name. + /// + protected abstract string DefaultFileExt { get; } + + protected AnalyzerTest() + { + TestState = new SolutionState(DefaultTestProjectName, Language, DefaultFilePathPrefix, DefaultFileExt); + } + + /// + /// Gets the language name used for the test. + /// + /// + /// The language name used for the test. + /// + public abstract string Language { get; } + + /// + /// Sets the input source file for analyzer or code fix testing. + /// + /// + public string TestCode + { + set + { + if (value != null) + { + TestState.Sources.Add(value); + } + } + } + + /// + /// Gets the list of diagnostics expected in the source(s) and/or additonal files. + /// + public List ExpectedDiagnostics => TestState.ExpectedDiagnostics; + + /// + /// Gets or sets the behavior of compiler diagnostics in validation scenarios. The default value is + /// . + /// + public CompilerDiagnostics CompilerDiagnostics { get; set; } = CompilerDiagnostics.Errors; + + /// + /// Gets or sets options for the markup processor when markup is used for diagnostics. The default value is + /// . + /// + public MarkupOptions MarkupOptions { get; set; } + + public SolutionState TestState { get; } + + /// + /// 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(); + + /// + /// Gets or sets the test behaviors applying to this analyzer. The default value is + /// . + /// + public TestBehaviors TestBehaviors { get; set; } + + /// + /// Gets a collection of diagnostics to explicitly disable in the for projects. + /// + public List DisabledDiagnostics { get; } = new List(); + + /// + /// Gets or sets the default reference assemblies to use. + /// + /// + public ReferenceAssemblies ReferenceAssemblies { get; set; } = ReferenceAssemblies.Default; + + /// + /// Gets or sets an additional verifier for a diagnostic. + /// The action compares actual and the expected + /// based on custom test requirements not yet supported by the test framework. + /// + public Action? DiagnosticVerifier { get; set; } + + /// + /// Gets a collection of transformation functions to apply to during diagnostic + /// or code fix test setup. + /// + public List> OptionsTransforms { get; } = new List>(); + + /// + /// Gets a collection of transformation functions to apply to a during diagnostic or code + /// fix test setup. + /// + public List> SolutionTransforms { get; } = new List>(); + + /// + /// Gets or sets the timeout to use when matching expected and actual diagnostics. The default value is 2 + /// seconds. + /// + protected TimeSpan MatchDiagnosticsTimeout { get; set; } = TimeSpan.FromSeconds(2); + + private readonly ConcurrentBag _workspaces = new ConcurrentBag(); + + /// + /// Runs the test. + /// + /// The that the operation will observe. + /// A representing the asynchronous operation. + public async Task RunAsync(CancellationToken cancellationToken = default) + { + try + { + await RunImplAsync(cancellationToken); + } + finally + { + while (_workspaces.TryTake(out var workspace)) + { + workspace.Dispose(); + } + } + } + + /// + /// Runs the test. + /// + /// The that the operation will observe. + /// A representing the asynchronous operation. + protected virtual async Task RunImplAsync(CancellationToken cancellationToken) + { + Verify.NotEmpty($"{nameof(TestState)}.{nameof(SolutionState.Sources)}", TestState.Sources); + + var analyzers = GetDiagnosticAnalyzers().ToArray(); + var defaultDiagnostic = GetDefaultDiagnostic(analyzers); + var supportedDiagnostics = analyzers.SelectMany(analyzer => analyzer.SupportedDiagnostics).ToImmutableArray(); + var fixableDiagnostics = ImmutableArray.Empty; + var testState = TestState.WithInheritedValuesApplied(null, fixableDiagnostics).WithProcessedMarkup(MarkupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, DefaultFilePath); + + await VerifyDiagnosticsAsync(new EvaluatedProjectState(testState, ReferenceAssemblies), testState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), testState.ExpectedDiagnostics.ToArray(), Verify, cancellationToken).ConfigureAwait(false); + } + + /// + /// Gets the default diagnostic to use during markup processing. By default, the single diagnostic of + /// the first analyzer is used, and no default diagonostic is available if multiple diagnostics are provided by + /// the analyzer. If is used, the first available diagnostic + /// is used. + /// + /// The analyzers to consider. + /// The default diagnostic to use during markup processing. + protected internal virtual DiagnosticDescriptor? GetDefaultDiagnostic(DiagnosticAnalyzer[] analyzers) + { + if (analyzers.Length == 0) + { + return null; + } + + if (MarkupOptions.HasFlag(MarkupOptions.UseFirstDescriptor)) + { + foreach (var analyzer in analyzers) + { + if (analyzer.SupportedDiagnostics.Any()) + { + return analyzer.SupportedDiagnostics[0]; + } + } + + return null; + } + else if (analyzers[0].SupportedDiagnostics.Length == 1) + { + return analyzers[0].SupportedDiagnostics[0]; + } + else + { + return null; + } + } + + protected string FormatVerifierMessage(ImmutableArray analyzers, Diagnostic actual, DiagnosticResult expected, string message) + { + return $"{message}{Environment.NewLine}" + + $"{Environment.NewLine}" + + $"Expected diagnostic:{Environment.NewLine}" + + $" {FormatDiagnostics(analyzers, DefaultFilePath, expected)}{Environment.NewLine}" + + $"Actual diagnostic:{Environment.NewLine}" + + $" {FormatDiagnostics(analyzers, DefaultFilePath, actual)}{Environment.NewLine}"; + } + + /// + /// General method that gets a collection of actual s found in the source after the + /// analyzer is run, then verifies each of them. + /// + /// The primary project. + /// Additional projects to include in the solution. + /// A collection of s that should appear after the analyzer + /// is run on the sources. + /// The verifier to use for test assertions. + /// The that the task will observe. + /// A representing the asynchronous operation. + protected async Task VerifyDiagnosticsAsync(EvaluatedProjectState primaryProject, ImmutableArray additionalProjects, DiagnosticResult[] expected, IVerifier verifier, CancellationToken cancellationToken) + { + (string filename, SourceText content)[] sources = primaryProject.Sources.ToArray(); + + var analyzers = GetDiagnosticAnalyzers().ToImmutableArray(); + VerifyDiagnosticResults(await GetSortedDiagnosticsAsync(primaryProject, additionalProjects, analyzers, verifier, cancellationToken).ConfigureAwait(false), analyzers, expected, verifier); + await VerifyGeneratedCodeDiagnosticsAsync(analyzers, sources, primaryProject, additionalProjects, expected, verifier, cancellationToken).ConfigureAwait(false); + await VerifySuppressionDiagnosticsAsync(analyzers, sources, primaryProject, additionalProjects, expected, verifier, cancellationToken).ConfigureAwait(false); + } + + private async Task VerifyGeneratedCodeDiagnosticsAsync(ImmutableArray analyzers, (string filename, SourceText content)[] sources, EvaluatedProjectState primaryProject, ImmutableArray additionalProjects, DiagnosticResult[] expected, IVerifier verifier, CancellationToken cancellationToken) + { + if (TestBehaviors.HasFlag(TestBehaviors.SkipGeneratedCodeCheck) + || analyzers.All(analyzer => AnalyzerInfo.HasConfiguredGeneratedCodeAnalysis(analyzer))) + { + return; + } + + if (!expected.Any(x => IsSubjectToExclusion(x, analyzers, sources))) + { + return; + } + + // Diagnostics reported by the compiler and analyzer diagnostics which don't have a location will + // still be reported. We also insert a new line at the beginning so we have to move all diagnostic + // locations which have a specific position down by one line. + var expectedResults = expected + .Where(x => !IsSubjectToExclusion(x, analyzers, sources)) + .Select(x => IsInSourceFile(x, sources) ? x.WithLineOffset(1) : x) + .ToArray(); + + var generatedCodeVerifier = verifier.PushContext("Verifying exclusions in code"); + var commentPrefix = Language == LanguageNames.CSharp ? "//" : "'"; + var transformedProject = primaryProject.WithSources(primaryProject.Sources.Select(x => (x.filename, x.content.Replace(new TextSpan(0, 0), $" {commentPrefix} \r\n"))).ToImmutableArray()); + VerifyDiagnosticResults(await GetSortedDiagnosticsAsync(transformedProject, additionalProjects, analyzers, generatedCodeVerifier, cancellationToken).ConfigureAwait(false), analyzers, expectedResults, generatedCodeVerifier); + } + + private async Task VerifySuppressionDiagnosticsAsync(ImmutableArray analyzers, (string filename, SourceText content)[] sources, EvaluatedProjectState primaryProject, ImmutableArray additionalProjects, DiagnosticResult[] expected, IVerifier verifier, CancellationToken cancellationToken) + { + if (TestBehaviors.HasFlag(TestBehaviors.SkipSuppressionCheck)) + { + return; + } + + if (!expected.Any(x => IsSubjectToExclusion(x, analyzers, sources))) + { + return; + } + + // Diagnostics reported by the compiler and analyzer diagnostics which don't have a location will + // still be reported. We also insert a new line at the beginning so we have to move all diagnostic + // locations which have a specific position down by one line. + var expectedResults = expected + .Where(x => !IsSuppressible(analyzers, x, sources)) + .Select(x => IsInSourceFile(x, sources) ? x.WithLineOffset(1) : x) + .ToArray(); + + var prefix = Language == LanguageNames.CSharp ? "#pragma warning disable" : "#Disable Warning"; + var suppressionVerifier = verifier.PushContext($"Verifying exclusions in '{prefix}' code"); + var suppressedDiagnostics = expected.Where(x => IsSubjectToExclusion(x, analyzers, sources)).Select(x => x.Id).Distinct(); + var suppression = prefix + " " + string.Join(", ", suppressedDiagnostics); + var transformedProject = primaryProject.WithSources(primaryProject.Sources.Select(x => (x.filename, x.content.Replace(new TextSpan(0, 0), $"{suppression}\r\n"))).ToImmutableArray()); + VerifyDiagnosticResults(await GetSortedDiagnosticsAsync(transformedProject, additionalProjects, analyzers, suppressionVerifier, cancellationToken).ConfigureAwait(false), analyzers, expectedResults, suppressionVerifier); + } + + /// + /// Checks each of the actual s found and compares them with the corresponding + /// in the array of expected results. s are considered + /// equal only if the , , + /// , and of the + /// match the actual . + /// + /// The s found by the compiler after running the analyzer + /// on the source code. + /// The analyzers that have been run on the sources. + /// A collection of s describing the expected + /// diagnostics for the sources. + /// The verifier to use for test assertions. + private void VerifyDiagnosticResults(IEnumerable<(Project project, Diagnostic diagnostic)> actualResults, ImmutableArray analyzers, DiagnosticResult[] expectedResults, IVerifier verifier) + { + var matchedDiagnostics = MatchDiagnostics(actualResults.ToArray(), expectedResults); + verifier.Equal(actualResults.Count(), matchedDiagnostics.Count(x => x.actual is object), $"{nameof(MatchDiagnostics)} failed to include all actual diagnostics in the result"); + verifier.Equal(expectedResults.Length, matchedDiagnostics.Count(x => x.expected is object), $"{nameof(MatchDiagnostics)} failed to include all expected diagnostics in the result"); + + actualResults = matchedDiagnostics.Select(x => x.actual).Where(x => x is { }).Select(x => x!.Value); + expectedResults = matchedDiagnostics.Where(x => x.expected is object).Select(x => x.expected.GetValueOrDefault()).ToArray(); + + var expectedCount = expectedResults.Length; + var actualCount = actualResults.Count(); + + var diagnosticsOutput = actualResults.Any() ? FormatDiagnostics(analyzers, DefaultFilePath, actualResults.Select(result => result.diagnostic).ToArray()) : " NONE."; + var message = $"Mismatch between number of diagnostics returned, expected \"{expectedCount}\" actual \"{actualCount}\"\r\n\r\nDiagnostics:\r\n{diagnosticsOutput}\r\n"; + verifier.Equal(expectedCount, actualCount, message); + + for (var i = 0; i < expectedResults.Length; i++) + { + var actual = actualResults.ElementAt(i); + var expected = expectedResults[i]; + + if (!expected.HasLocation) + { + message = FormatVerifierMessage(analyzers, actual.diagnostic, expected, "Expected a project diagnostic with no location:"); + verifier.Equal(Location.None, actual.diagnostic.Location, message); + } + else + { + VerifyDiagnosticLocation(analyzers, actual.diagnostic, expected, actual.diagnostic.Location, expected.Spans[0], verifier); + if (!expected.Options.HasFlag(DiagnosticOptions.IgnoreAdditionalLocations)) + { + var additionalLocations = actual.diagnostic.AdditionalLocations.ToArray(); + + message = FormatVerifierMessage(analyzers, actual.diagnostic, expected, $"Expected {expected.Spans.Length - 1} additional locations but got {additionalLocations.Length} for Diagnostic:"); + verifier.Equal(expected.Spans.Length - 1, additionalLocations.Length, message); + + for (var j = 0; j < additionalLocations.Length; ++j) + { + VerifyDiagnosticLocation(analyzers, actual.diagnostic, expected, additionalLocations[j], expected.Spans[j + 1], verifier); + } + } + } + + message = FormatVerifierMessage(analyzers, actual.diagnostic, expected, $"Expected diagnostic id to be \"{expected.Id}\" was \"{actual.diagnostic.Id}\""); + verifier.Equal(expected.Id, actual.diagnostic.Id, message); + + if (!expected.Options.HasFlag(DiagnosticOptions.IgnoreSeverity)) + { + message = FormatVerifierMessage(analyzers, actual.diagnostic, expected, $"Expected diagnostic severity to be \"{expected.Severity}\" was \"{actual.diagnostic.Severity}\""); + verifier.Equal(expected.Severity, actual.diagnostic.Severity, message); + } + + if (expected.Message != null) + { + message = FormatVerifierMessage(analyzers, actual.diagnostic, expected, $"Expected diagnostic message to be \"{expected.Message}\" was \"{actual.diagnostic.GetMessage()}\""); + verifier.Equal(expected.Message, actual.diagnostic.GetMessage(), message); + } + else if (expected.MessageArguments?.Length > 0) + { + message = FormatVerifierMessage(analyzers, actual.diagnostic, expected, $"Expected diagnostic message arguments to match"); + verifier.SequenceEqual( + expected.MessageArguments.Select(argument => argument?.ToString() ?? string.Empty), + GetArguments(actual.diagnostic).Select(argument => argument?.ToString() ?? string.Empty), + StringComparer.Ordinal, + message); + } + + DiagnosticVerifier?.Invoke(actual.diagnostic, expected, verifier); + } + } + + /// + /// Match actual diagnostics with expected diagnostics. + /// + /// + /// While each actual diagnostic contains complete information about the diagnostic (location, severity, + /// message, etc.), the expected diagnostics sometimes contain partial information. It is therefore possible for + /// an expected diagnostic to match more than one actual diagnostic, while another expected diagnostic with more + /// complete information only matches a single specific actual diagnostic. + /// + /// This method attempts to find a best matching of actual and expected diagnostics. + /// + /// The actual diagnostics reported by analysis. + /// The expected diagnostics. + /// + /// A collection of matched diagnostics, with the following characteristics: + /// + /// + /// Every element of will appear exactly once as the first element of an item in the result. + /// Every element of will appear exactly once as the second element of an item in the result. + /// An item in the result which specifies both a and a indicates a matched pair, i.e. the actual and expected results are believed to refer to the same diagnostic. + /// An item in the result which specifies only a indicates an actual diagnostic for which no matching expected diagnostic was found. + /// An item in the result which specifies only a indicates an expected diagnostic for which no matching actual diagnostic was found. + /// + /// If no exact match is found (all actual diagnostics are matched to an expected diagnostic without + /// errors), this method is allowed to attempt fall-back matching using a strategy intended to minimize + /// the total number of mismatched pairs. + /// + /// + private ImmutableArray<((Project project, Diagnostic diagnostic)? actual, DiagnosticResult? expected)> MatchDiagnostics((Project project, Diagnostic diagnostic)[] actualResults, DiagnosticResult[] expectedResults) + { + var actualIds = actualResults.Select(result => result.diagnostic.Id).ToImmutableArray(); + var actualResultLocations = actualResults.Select(result => (location: result.diagnostic.Location.GetLineSpan(), additionalLocations: result.diagnostic.AdditionalLocations.Select(location => location.GetLineSpan()).ToImmutableArray())).ToImmutableArray(); + var actualArguments = actualResults.Select(actual => GetArguments(actual.diagnostic).Select(argument => argument?.ToString() ?? string.Empty).ToImmutableArray()).ToImmutableArray(); + + expectedResults = expectedResults.ToOrderedArray(); + var expectedArguments = expectedResults.Select(expected => expected.MessageArguments?.Select(argument => argument?.ToString() ?? string.Empty).ToImmutableArray() ?? ImmutableArray.Empty).ToImmutableArray(); + + // Initialize the best match to a trivial result where everything is unmatched. This will be updated if/when + // better matches are found. + var bestMatchCount = MatchQuality.RemainingUnmatched(actualResults.Length + expectedResults.Length); + var bestMatch = actualResults.Select(result => (((Project project, Diagnostic diagnostic)?)result, default(DiagnosticResult?))).Concat(expectedResults.Select(result => (default((Project project, Diagnostic diagnostic)?), (DiagnosticResult?)result))).ToImmutableArray(); + + var builder = ImmutableArray.CreateBuilder<((Project project, Diagnostic diagnostic)? actual, DiagnosticResult? expected)>(); + var usedExpected = new bool[expectedResults.Length]; + + // The recursive match algorithm is not optimized, so use a timeout to ensure it completes in a reasonable + // time if a correct match isn't found. + using var cancellationTokenSource = new CancellationTokenSource(MatchDiagnosticsTimeout); + + try + { + _ = RecursiveMatch(0, actualResults.Length, 0, expectedArguments.Length, MatchQuality.Full, usedExpected); + } + catch (OperationCanceledException) when (cancellationTokenSource.IsCancellationRequested) + { + // Continue with the best match we have + } + + return bestMatch; + + // Match items using recursive backtracking. Returns the distance the best match under this path is from an + // ideal result of 0 (1:1 matching of actual and expected results). Currently the distance is calculated as + // the sum of the match values: + // + // * Fully-matched items have a value of MatchQuality.Full. + // * Partially-matched items have a value between MatchQuality.Full and MatchQuality.None (exclusive). + // * Fully-unmatched items have a value of MatchQuality.None. + MatchQuality RecursiveMatch(int firstActualIndex, int remainingActualItems, int firstExpectedIndex, int remainingExpectedItems, MatchQuality unmatchedActualResults, bool[] usedExpected) + { + var matchedOnEntry = actualResults.Length - remainingActualItems; + var bestPossibleUnmatchedExpected = MatchQuality.RemainingUnmatched(Math.Abs(remainingActualItems - remainingExpectedItems)); + var bestPossible = unmatchedActualResults + bestPossibleUnmatchedExpected; + + if (firstActualIndex == actualResults.Length) + { + // We reached the end of the actual diagnostics. Any remaning unmatched expected diagnostics should + // be added to the end. If this path produced a better result than the best known path so far, + // update the best match to this one. + var totalUnmatched = unmatchedActualResults + MatchQuality.RemainingUnmatched(remainingExpectedItems); + + // Avoid manipulating the builder if we know the current path is no better than the previous best. + if (totalUnmatched < bestMatchCount) + { + var addedCount = 0; + + // Add the remaining unmatched expected diagnostics + for (var i = firstExpectedIndex; i < expectedResults.Length; i++) + { + if (!usedExpected[i]) + { + addedCount++; + builder.Add((null, (DiagnosticResult?)expectedResults[i])); + } + } + + bestMatchCount = totalUnmatched; + bestMatch = builder.ToImmutable(); + + for (var i = 0; i < addedCount; i++) + { + builder.RemoveAt(builder.Count - 1); + } + } + + return totalUnmatched; + } + + cancellationTokenSource.Token.ThrowIfCancellationRequested(); + + var currentBest = unmatchedActualResults + MatchQuality.RemainingUnmatched(remainingActualItems + remainingExpectedItems); + for (var i = firstExpectedIndex; i < expectedResults.Length; i++) + { + if (usedExpected[i]) + { + continue; + } + + var (lineSpan, additionalLineSpans) = actualResultLocations[firstActualIndex]; + var matchValue = GetMatchValue(actualResults[firstActualIndex].diagnostic, actualIds[firstActualIndex], lineSpan, additionalLineSpans, actualArguments[firstActualIndex], expectedResults[i], expectedArguments[i]); + if (matchValue == MatchQuality.None) + { + continue; + } + + try + { + usedExpected[i] = true; + builder.Add((actualResults[firstActualIndex], expectedResults[i])); + var bestResultWithCurrentMatch = RecursiveMatch(firstActualIndex + 1, remainingActualItems - 1, i == firstExpectedIndex ? firstExpectedIndex + 1 : firstExpectedIndex, remainingExpectedItems - 1, unmatchedActualResults + matchValue, usedExpected); + currentBest = Min(bestResultWithCurrentMatch, currentBest); + if (currentBest == bestPossible) + { + // Return immediately if we know the current actual result cannot be paired with a different + // expected result to produce a better match. + return bestPossible; + } + } + finally + { + usedExpected[i] = false; + builder.RemoveAt(builder.Count - 1); + } + } + + if (currentBest > unmatchedActualResults) + { + // We might be able to improve the results by leaving the current actual diagnostic unmatched + try + { + builder.Add((actualResults[firstActualIndex], null)); + var bestResultWithCurrentUnmatched = RecursiveMatch(firstActualIndex + 1, remainingActualItems - 1, firstExpectedIndex, remainingExpectedItems, unmatchedActualResults + MatchQuality.None, usedExpected); + return Min(bestResultWithCurrentUnmatched, currentBest); + } + finally + { + builder.RemoveAt(builder.Count - 1); + } + } + + Debug.Assert(currentBest == unmatchedActualResults, $"Assertion failure: {currentBest} == {unmatchedActualResults}"); + return currentBest; + } + + static MatchQuality Min(MatchQuality val1, MatchQuality val2) + => val2 < val1 ? val2 : val1; + + static MatchQuality GetMatchValue(Diagnostic diagnostic, string diagnosticId, FileLinePositionSpan lineSpan, ImmutableArray additionalLineSpans, ImmutableArray actualArguments, DiagnosticResult diagnosticResult, ImmutableArray expectedArguments) + { + // A full match automatically gets the value MatchQuality.Full. A partial match gets a "point" for each + // of the following elements: + // + // 1. Diagnostic span start + // 2. Diagnostic span end + // 3. Diagnostic ID + // + // A partial match starts at MatchQuality.None, with a point deduction for each of the above matching + // items. + var isLocationMatch = IsLocationMatch(diagnostic, lineSpan, additionalLineSpans, diagnosticResult, out var matchSpanStart, out var matchSpanEnd); + var isIdMatch = diagnosticId == diagnosticResult.Id; + if (isLocationMatch + && isIdMatch + && IsSeverityMatch(diagnostic, diagnosticResult) + && IsMessageMatch(diagnostic, actualArguments, diagnosticResult, expectedArguments)) + { + return MatchQuality.Full; + } + + var points = (matchSpanStart ? 1 : 0) + (matchSpanEnd ? 1 : 0) + (isIdMatch ? 1 : 0); + if (points == 0) + { + return MatchQuality.None; + } + + return new MatchQuality(4 - points); + } + + static bool IsLocationMatch(Diagnostic diagnostic, FileLinePositionSpan lineSpan, ImmutableArray additionalLineSpans, DiagnosticResult diagnosticResult, out bool matchSpanStart, out bool matchSpanEnd) + { + if (!diagnosticResult.HasLocation) + { + matchSpanStart = false; + matchSpanEnd = false; + return Equals(Location.None, diagnostic.Location); + } + else + { + if (!IsLocationMatch2(diagnostic.Location, lineSpan, diagnosticResult.Spans[0], out matchSpanStart, out matchSpanEnd)) + { + return false; + } + + if (diagnosticResult.Options.HasFlag(DiagnosticOptions.IgnoreAdditionalLocations)) + { + return true; + } + + var additionalLocations = diagnostic.AdditionalLocations.ToArray(); + if (additionalLocations.Length != diagnosticResult.Spans.Length - 1) + { + // Number of additional locations does not match expected result + return false; + } + + for (var i = 0; i < additionalLocations.Length; i++) + { + if (!IsLocationMatch2(additionalLocations[i], additionalLineSpans[i], diagnosticResult.Spans[i + 1], out _, out _)) + { + return false; + } + } + + return true; + } + } + + static bool IsLocationMatch2(Location actual, FileLinePositionSpan actualSpan, DiagnosticLocation expected, out bool matchSpanStart, out bool matchSpanEnd) + { + matchSpanStart = actualSpan.StartLinePosition == expected.Span.StartLinePosition; + matchSpanEnd = expected.Options.HasFlag(DiagnosticLocationOptions.IgnoreLength) + || actualSpan.EndLinePosition == expected.Span.EndLinePosition; + + var assert = actualSpan.Path == expected.Span.Path || (actualSpan.Path?.Contains("Test0.") == true && expected.Span.Path.Contains("Test.")); + if (!assert) + { + // Expected diagnostic to be in file "{expected.Span.Path}" was actually in file "{actualSpan.Path}" + return false; + } + + if (!matchSpanStart || !matchSpanEnd) + { + return false; + } + + return true; + } + + static bool IsSeverityMatch(Diagnostic actual, DiagnosticResult expected) + { + if (expected.Options.HasFlag(DiagnosticOptions.IgnoreSeverity)) + { + return true; + } + + return actual.Severity == expected.Severity; + } + + static bool IsMessageMatch(Diagnostic actual, ImmutableArray actualArguments, DiagnosticResult expected, ImmutableArray expectedArguments) + { + if (expected.Message is null) + { + if (expected.MessageArguments?.Length > 0) + { + return actualArguments.SequenceEqual(expectedArguments); + } + + return true; + } + + return string.Equals(expected.Message, actual.GetMessage()); + } + } + + /// + /// Helper method to that checks the location of a + /// and compares it with the location described by a + /// . + /// + /// The analyzer that have been run on the sources. + /// The diagnostic that was found in the code. + /// The expected diagnostic. + /// The location of the diagnostic found in the code. + /// The describing the expected location of the + /// diagnostic. + /// The verifier to use for test assertions. + private void VerifyDiagnosticLocation(ImmutableArray analyzers, Diagnostic diagnostic, DiagnosticResult expectedDiagnostic, Location actual, DiagnosticLocation expected, IVerifier verifier) + { + var actualSpan = actual.GetLineSpan(); + + var assert = actualSpan.Path == expected.Span.Path || (actualSpan.Path?.Contains("Test0.") == true && expected.Span.Path.Contains("Test.")); + + var message = FormatVerifierMessage(analyzers, diagnostic, expectedDiagnostic, $"Expected diagnostic to be in file \"{expected.Span.Path}\" was actually in file \"{actualSpan.Path}\""); + verifier.True(assert, message); + + VerifyLinePosition(analyzers, diagnostic, expectedDiagnostic, actualSpan.StartLinePosition, expected.Span.StartLinePosition, "start", verifier); + if (!expected.Options.HasFlag(DiagnosticLocationOptions.IgnoreLength)) + { + VerifyLinePosition(analyzers, diagnostic, expectedDiagnostic, actualSpan.EndLinePosition, expected.Span.EndLinePosition, "end", verifier); + } + } + + private void VerifyLinePosition(ImmutableArray analyzers, Diagnostic diagnostic, DiagnosticResult expectedDiagnostic, LinePosition actualLinePosition, LinePosition expectedLinePosition, string positionText, IVerifier verifier) + { + var message = FormatVerifierMessage(analyzers, diagnostic, expectedDiagnostic, $"Expected diagnostic to {positionText} on line \"{expectedLinePosition.Line + 1}\" was actually on line \"{actualLinePosition.Line + 1}\""); + verifier.Equal( + expectedLinePosition.Line, + actualLinePosition.Line, + message); + + message = FormatVerifierMessage(analyzers, diagnostic, expectedDiagnostic, $"Expected diagnostic to {positionText} at column \"{expectedLinePosition.Character + 1}\" was actually at column \"{actualLinePosition.Character + 1}\""); + verifier.Equal( + expectedLinePosition.Character, + actualLinePosition.Character, + message); + } + + /// + /// Helper method to format a into an easily readable string. + /// + /// The analyzers that this verifier tests. + /// The default file path for diagnostics. + /// A collection of s to be formatted. + /// The formatted as a string. + private static string FormatDiagnostics(ImmutableArray analyzers, string defaultFilePath, params Diagnostic[] diagnostics) + { + var builder = new StringBuilder(); + for (var i = 0; i < diagnostics.Length; ++i) + { + var diagnosticsId = diagnostics[i].Id; + var location = diagnostics[i].Location; + + builder.Append("// ").AppendLine(diagnostics[i].ToString()); + + var applicableAnalyzer = analyzers.FirstOrDefault(a => a.SupportedDiagnostics.Any(dd => dd.Id == diagnosticsId)); + if (applicableAnalyzer != null) + { + var analyzerType = applicableAnalyzer.GetType(); + var rule = location != Location.None && location.IsInSource && applicableAnalyzer.SupportedDiagnostics.Length == 1 ? string.Empty : $"{analyzerType.Name}.{diagnosticsId}"; + + if (location == Location.None || !location.IsInSource) + { + builder.Append($"new DiagnosticResult({rule})"); + } + else + { + var resultMethodName = location.SourceTree.FilePath.EndsWith(".cs") ? "VerifyCS.Diagnostic" : "VerifyVB.Diagnostic"; + builder.Append($"{resultMethodName}({rule})"); + } + } + else + { + builder.Append( + diagnostics[i].Severity switch + { + DiagnosticSeverity.Error => $"{nameof(DiagnosticResult)}.{nameof(DiagnosticResult.CompilerError)}(\"{diagnostics[i].Id}\")", + DiagnosticSeverity.Warning => $"{nameof(DiagnosticResult)}.{nameof(DiagnosticResult.CompilerWarning)}(\"{diagnostics[i].Id}\")", + var severity => $"new {nameof(DiagnosticResult)}(\"{diagnostics[i].Id}\", {nameof(DiagnosticSeverity)}.{severity})", + }); + } + + if (location == Location.None) + { + // No additional location data needed + } + else + { + AppendLocation(diagnostics[i].Location); + foreach (var additionalLocation in diagnostics[i].AdditionalLocations) + { + AppendLocation(additionalLocation); + } + } + + var arguments = GetArguments(diagnostics[i]); + if (arguments.Count > 0) + { + builder.Append($".{nameof(DiagnosticResult.WithArguments)}("); + builder.Append(string.Join(", ", arguments.Select(a => "\"" + a?.ToString() + "\""))); + builder.Append(")"); + } + + builder.AppendLine(","); + } + + return builder.ToString(); + + // Local functions + void AppendLocation(Location location) + { + var lineSpan = location.GetLineSpan(); + var pathString = location.IsInSource && lineSpan.Path == defaultFilePath ? string.Empty : $"\"{lineSpan.Path}\", "; + var linePosition = lineSpan.StartLinePosition; + var endLinePosition = lineSpan.EndLinePosition; + builder.Append($".WithSpan({pathString}{linePosition.Line + 1}, {linePosition.Character + 1}, {endLinePosition.Line + 1}, {endLinePosition.Character + 1})"); + } + } + + /// + /// Helper method to format a into an easily readable string. + /// + /// The analyzers that this verifier tests. + /// The default file path for diagnostics. + /// A collection of s to be formatted. + /// The formatted as a string. + private static string FormatDiagnostics(ImmutableArray analyzers, string defaultFilePath, params DiagnosticResult[] diagnostics) + { + var builder = new StringBuilder(); + for (var i = 0; i < diagnostics.Length; ++i) + { + var diagnosticsId = diagnostics[i].Id; + + builder.Append("// ").AppendLine(diagnostics[i].ToString()); + + var applicableAnalyzer = analyzers.FirstOrDefault(a => a.SupportedDiagnostics.Any(dd => dd.Id == diagnosticsId)); + if (applicableAnalyzer != null) + { + var analyzerType = applicableAnalyzer.GetType(); + var rule = diagnostics[i].HasLocation && applicableAnalyzer.SupportedDiagnostics.Length == 1 ? string.Empty : $"{analyzerType.Name}.{diagnosticsId}"; + + if (!diagnostics[i].HasLocation) + { + builder.Append($"new DiagnosticResult({rule})"); + } + else + { + var resultMethodName = diagnostics[i].Spans[0].Span.Path.EndsWith(".cs") ? "VerifyCS.Diagnostic" : "VerifyVB.Diagnostic"; + builder.Append($"{resultMethodName}({rule})"); + } + } + else + { + builder.Append( + diagnostics[i].Severity switch + { + DiagnosticSeverity.Error => $"{nameof(DiagnosticResult)}.{nameof(DiagnosticResult.CompilerError)}(\"{diagnostics[i].Id}\")", + DiagnosticSeverity.Warning => $"{nameof(DiagnosticResult)}.{nameof(DiagnosticResult.CompilerWarning)}(\"{diagnostics[i].Id}\")", + var severity => $"new {nameof(DiagnosticResult)}(\"{diagnostics[i].Id}\", {nameof(DiagnosticSeverity)}.{severity})", + }); + } + + if (!diagnostics[i].HasLocation) + { + // No additional location data needed + } + else + { + foreach (var span in diagnostics[i].Spans) + { + AppendLocation(span); + if (diagnostics[i].Options.HasFlag(DiagnosticOptions.IgnoreAdditionalLocations)) + { + break; + } + } + } + + var arguments = diagnostics[i].MessageArguments; + if (arguments?.Length > 0) + { + builder.Append($".{nameof(DiagnosticResult.WithArguments)}("); + builder.Append(string.Join(", ", arguments.Select(a => "\"" + a?.ToString() + "\""))); + builder.Append(")"); + } + + builder.AppendLine(","); + } + + return builder.ToString(); + + // Local functions + void AppendLocation(DiagnosticLocation location) + { + var pathString = location.Span.Path == defaultFilePath ? string.Empty : $"\"{location.Span.Path}\", "; + var linePosition = location.Span.StartLinePosition; + + if (location.Options.HasFlag(DiagnosticLocationOptions.IgnoreLength)) + { + builder.Append($".WithLocation({pathString}{linePosition.Line + 1}, {linePosition.Character + 1})"); + } + else + { + var endLinePosition = location.Span.EndLinePosition; + builder.Append($".WithSpan({pathString}{linePosition.Line + 1}, {linePosition.Character + 1}, {endLinePosition.Line + 1}, {endLinePosition.Character + 1})"); + } + } + } + + private static bool IsSubjectToExclusion(DiagnosticResult result, ImmutableArray analyzers, (string filename, SourceText content)[] sources) + { + if (result.Id.StartsWith("CS", StringComparison.Ordinal) + || result.Id.StartsWith("BC", StringComparison.Ordinal)) + { + // This is a compiler diagnostic + return false; + } + + if (result.Id.StartsWith("AD", StringComparison.Ordinal)) + { + // This diagnostic is reported by the analyzer infrastructure + return false; + } + + if (result.Spans.IsEmpty) + { + return false; + } + + if (!IsInSourceFile(result, sources)) + { + // This diagnostic is not reported in a source file + return false; + } + + if (!analyzers.Any(analyzer => analyzer.SupportedDiagnostics.Any(supported => supported.Id == result.Id))) + { + // This diagnostic is not reported by an active analyzer + return false; + } + + return true; + } + + private static bool IsSuppressible(ImmutableArray analyzers, DiagnosticResult result, (string filename, SourceText content)[] sources) + { + if (!IsSubjectToExclusion(result, analyzers, sources)) + { + return false; + } + + foreach (var analyzer in analyzers) + { + foreach (var diagnostic in analyzer.SupportedDiagnostics) + { + if (diagnostic.Id != result.Id) + { + continue; + } + + if (diagnostic.CustomTags.Contains(WellKnownDiagnosticTags.NotConfigurable)) + { + return false; + } + } + } + + return true; + } + + private static bool IsInSourceFile(DiagnosticResult result, (string filename, SourceText content)[] sources) + { + if (!result.HasLocation) + { + return false; + } + + return sources.Any(source => source.filename.Equals(result.Spans[0].Span.Path)); + } + + /// + /// Given classes in the form of strings, their language, and an to apply to + /// it, return the s found in the string after converting it to a + /// . + /// + /// The primary project. + /// Additional projects to include in the solution. + /// The analyzers to be run on the sources. + /// The verifier to use for test assertions. + /// The that the task will observe. + /// A collection of s that surfaced in the source code, sorted by + /// . + private async Task> GetSortedDiagnosticsAsync(EvaluatedProjectState primaryProject, ImmutableArray additionalProjects, ImmutableArray analyzers, IVerifier verifier, CancellationToken cancellationToken) + { + var solution = await GetSolutionAsync(primaryProject, additionalProjects, verifier, cancellationToken); + var primaryProjectInSolution = solution.Projects.Single(project => project.Name == DefaultTestProjectName); + var additionalDiagnostics = primaryProject.AdditionalDiagnostics.Select(diagnostic => (primaryProjectInSolution, diagnostic)).ToImmutableArray(); + foreach (var additionalProject in additionalProjects) + { + var additionalProjectInSolution = solution.Projects.Single(project => project.Name == additionalProject.Name); + additionalDiagnostics = additionalDiagnostics.AddRange(additionalProject.AdditionalDiagnostics.Select(diagnostic => (additionalProjectInSolution, diagnostic))); + } + + return await GetSortedDiagnosticsAsync(solution, analyzers, additionalDiagnostics, CompilerDiagnostics, verifier, cancellationToken); + } + + /// + /// Given an analyzer and a collection of documents to apply it to, run the analyzer and gather an array of + /// diagnostics found. The returned diagnostics are then ordered by location in the source documents. + /// + /// The that the analyzer(s) will be run on. + /// The analyzer to run on the documents. + /// Additional diagnostics reported for the solution, which need to be verified. + /// The behavior of compiler diagnostics in validation scenarios. + /// The verifier to use for test assertions. + /// The that the task will observe. + /// A collection of s that surfaced in the source code, sorted by + /// . + protected async Task> GetSortedDiagnosticsAsync(Solution solution, ImmutableArray analyzers, ImmutableArray<(Project project, Diagnostic diagnostic)> additionalDiagnostics, CompilerDiagnostics compilerDiagnostics, IVerifier verifier, CancellationToken cancellationToken) + { + if (analyzers.IsEmpty) + { + analyzers = ImmutableArray.Create(new EmptyDiagnosticAnalyzer()); + } + + var diagnostics = ImmutableArray.CreateBuilder<(Project project, Diagnostic diagnostic)>(); + foreach (var project in solution.Projects) + { + var compilation = await GetProjectCompilationAsync(project, verifier, cancellationToken).ConfigureAwait(false); + var compilationWithAnalyzers = compilation.WithAnalyzers(analyzers, GetAnalyzerOptions(project), cancellationToken); + var allDiagnostics = await compilationWithAnalyzers.GetAllDiagnosticsAsync().ConfigureAwait(false); + + diagnostics.AddRange(allDiagnostics.Where(diagnostic => !IsCompilerDiagnostic(diagnostic) || IsCompilerDiagnosticIncluded(diagnostic, compilerDiagnostics)).Select(diagnostic => (project, diagnostic))); + } + + diagnostics.AddRange(additionalDiagnostics); + var results = SortDistinctDiagnostics(diagnostics); + return results.ToImmutableArray(); + } + + protected virtual Task GetProjectCompilationAsync(Project project, IVerifier verifier, CancellationToken cancellationToken) + { + return project.GetCompilationAsync(cancellationToken); + } + + private static bool IsCompilerDiagnostic(Diagnostic diagnostic) + { + return diagnostic.Descriptor.CustomTags.Contains(WellKnownDiagnosticTags.Compiler); + } + + /// + /// Determines if a compiler diagnostic should be included for diagnostic validation. The default implementation includes all diagnostics at a severity level indicated by . + /// + /// The compiler diagnostic. + /// The compiler diagnostic level in effect for the test. + /// to include the diagnostic for validation; otherwise, to exclude a diagnostic. + protected virtual bool IsCompilerDiagnosticIncluded(Diagnostic diagnostic, CompilerDiagnostics compilerDiagnostics) + { + switch (compilerDiagnostics) + { + case CompilerDiagnostics.None: + default: + return false; + + case CompilerDiagnostics.Errors: + return diagnostic.Severity >= DiagnosticSeverity.Error; + + case CompilerDiagnostics.Warnings: + return diagnostic.Severity >= DiagnosticSeverity.Warning; + + case CompilerDiagnostics.Suggestions: + return diagnostic.Severity >= DiagnosticSeverity.Info; + + case CompilerDiagnostics.All: + return true; + } + } + + /// + /// Gets the effective analyzer options for a project. The default implementation returns + /// . + /// + /// The project. + /// The effective for the project. + protected virtual AnalyzerOptions GetAnalyzerOptions(Project project) + => project.AnalyzerOptions; + + /// + /// Given an array of strings as sources and a language, turn them into a and return the + /// solution. + /// + /// The primary project. + /// Additional projects to include in the solution. + /// The verifier to use for test assertions. + /// The that the task will observe. + /// A solution containing a project with the specified sources and additional files. + private async Task GetSolutionAsync(EvaluatedProjectState primaryProject, ImmutableArray additionalProjects, IVerifier verifier, CancellationToken cancellationToken) + { + verifier.LanguageIsSupported(Language); + + var project = await CreateProjectAsync(primaryProject, additionalProjects, cancellationToken); + var documents = project.Documents.ToArray(); + + verifier.Equal(primaryProject.Sources.Length, documents.Length, "Amount of sources did not match amount of Documents created"); + + return 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 . + /// + /// The primary project. + /// Additional projects to include in the solution. + /// The that the task will observe. + /// A created out of the s created from the source + /// strings. + protected async Task CreateProjectAsync(EvaluatedProjectState primaryProject, ImmutableArray additionalProjects, CancellationToken cancellationToken) + { + var project = await CreateProjectImplAsync(primaryProject, additionalProjects, cancellationToken); + return ApplyCompilationOptions(project); + } + + /// + /// Create a project using the input strings as sources. + /// + /// The primary project. + /// Additional projects to include in the solution. + /// The that the task will observe. + /// A created out of the s created from the source + /// strings. + protected virtual async Task CreateProjectImplAsync(EvaluatedProjectState primaryProject, ImmutableArray additionalProjects, CancellationToken cancellationToken) + { + var fileNamePrefix = DefaultFilePathPrefix; + var fileExt = DefaultFileExt; + + var projectIdMap = new Dictionary(); + + var projectId = ProjectId.CreateNewId(debugName: primaryProject.Name); + projectIdMap.Add(primaryProject.Name, projectId); + var solution = await CreateSolutionAsync(projectId, primaryProject, cancellationToken); + + foreach (var projectState in additionalProjects) + { + var additionalProjectId = ProjectId.CreateNewId(debugName: projectState.Name); + projectIdMap.Add(projectState.Name, additionalProjectId); + + solution = solution.AddProject(additionalProjectId, projectState.Name, projectState.AssemblyName, projectState.Language); + + var referenceAssemblies = projectState.ReferenceAssemblies ?? ReferenceAssemblies; + + var xmlReferenceResolver = new TestXmlReferenceResolver(); + foreach (var xmlReference in XmlReferences) + { + xmlReferenceResolver.XmlReferences.Add(xmlReference.Key, xmlReference.Value); + } + + solution = solution.WithProjectCompilationOptions( + additionalProjectId, + solution.GetProject(additionalProjectId).CompilationOptions + .WithOutputKind(projectState.OutputKind) + .WithXmlReferenceResolver(xmlReferenceResolver) + .WithAssemblyIdentityComparer(referenceAssemblies.AssemblyIdentityComparer)); + + solution = solution.WithProjectParseOptions( + additionalProjectId, + solution.GetProject(additionalProjectId).ParseOptions + .WithDocumentationMode(projectState.DocumentationMode)); + + var metadataReferences = await referenceAssemblies.ResolveAsync(projectState.Language, cancellationToken); + solution = solution.AddMetadataReferences(additionalProjectId, metadataReferences) + .AddMetadataReferences(additionalProjectId, projectState.AdditionalReferences); + + foreach (var (newFileName, source) in projectState.Sources) + { + var documentId = DocumentId.CreateNewId(additionalProjectId, debugName: newFileName); + solution = solution.AddDocument(documentId, newFileName, source, filePath: newFileName); + } + + foreach (var (newFileName, source) in projectState.AdditionalFiles) + { + var documentId = DocumentId.CreateNewId(additionalProjectId, debugName: newFileName); + solution = solution.AddAdditionalDocument(documentId, newFileName, source, filePath: newFileName); + } + + foreach (var (newFileName, source) in projectState.AnalyzerConfigFiles) + { + var documentId = DocumentId.CreateNewId(additionalProjectId, debugName: newFileName); + solution = solution.AddAnalyzerConfigDocument(documentId, newFileName, source, filePath: newFileName); + } + } + + solution = solution.AddMetadataReferences(projectId, primaryProject.AdditionalReferences); + + foreach (var (newFileName, source) in primaryProject.Sources) + { + var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); + solution = solution.AddDocument(documentId, newFileName, source, filePath: newFileName); + } + + foreach (var (newFileName, source) in primaryProject.AdditionalFiles) + { + var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); + solution = solution.AddAdditionalDocument(documentId, newFileName, source, filePath: newFileName); + } + + foreach (var (newFileName, source) in primaryProject.AnalyzerConfigFiles) + { + var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); + solution = solution.AddAnalyzerConfigDocument(documentId, newFileName, source, filePath: newFileName); + } + + solution = AddProjectReferences(solution, projectId, primaryProject.AdditionalProjectReferences.Select(name => projectIdMap[name])); + foreach (var projectState in additionalProjects) + { + solution = AddProjectReferences(solution, projectIdMap[projectState.Name], projectState.AdditionalProjectReferences.Select(name => projectIdMap[name])); + } + + foreach (var transform in SolutionTransforms) + { + solution = transform(solution, projectId); + } + + return solution.GetProject(projectId); + + // Local functions + static Solution AddProjectReferences(Solution solution, ProjectId sourceProject, IEnumerable targetProjects) + { + return solution.AddProjectReferences(sourceProject, targetProjects.Select(id => new ProjectReference(id))); + } + } + + /// + /// Creates a solution that will be used as parent for the sources that need to be checked. + /// + /// The project identifier to use. + /// The primary project. + /// The that the task will observe. + /// The created solution. + protected virtual async Task CreateSolutionAsync(ProjectId projectId, EvaluatedProjectState projectState, CancellationToken cancellationToken) + { + var referenceAssemblies = projectState.ReferenceAssemblies ?? ReferenceAssemblies; + + var compilationOptions = CreateCompilationOptions() + .WithOutputKind(projectState.OutputKind); + + var xmlReferenceResolver = new TestXmlReferenceResolver(); + foreach (var xmlReference in XmlReferences) + { + xmlReferenceResolver.XmlReferences.Add(xmlReference.Key, xmlReference.Value); + } + + compilationOptions = compilationOptions + .WithXmlReferenceResolver(xmlReferenceResolver) + .WithAssemblyIdentityComparer(referenceAssemblies.AssemblyIdentityComparer); + + var parseOptions = CreateParseOptions() + .WithDocumentationMode(projectState.DocumentationMode); + + var workspace = CreateWorkspace(); + foreach (var transform in OptionsTransforms) + { + workspace.Options = transform(workspace.Options); + } + + var solution = workspace + .CurrentSolution + .AddProject(projectId, projectState.Name, projectState.Name, projectState.Language) + .WithProjectCompilationOptions(projectId, compilationOptions) + .WithProjectParseOptions(projectId, parseOptions); + + var metadataReferences = await referenceAssemblies.ResolveAsync(projectState.Language, cancellationToken); + solution = solution.AddMetadataReferences(projectId, metadataReferences); + + return solution; + } + + /// + /// Applies compilation options to a project. + /// + /// + /// The default implementation configures the project by enabling all supported diagnostics of analyzers + /// included in as well as AD0001. After configuring these + /// diagnostics, any diagnostic IDs indicated in are explicitly suppressed + /// using . + /// + /// The project. + /// The modified project. + protected virtual Project ApplyCompilationOptions(Project project) + { + var analyzers = GetDiagnosticAnalyzers(); + + var supportedDiagnosticsSpecificOptions = new Dictionary(); + foreach (var analyzer in analyzers) + { + foreach (var diagnostic in analyzer.SupportedDiagnostics) + { + // make sure the analyzers we are testing are enabled + if (diagnostic.IsEnabledByDefault) + { + supportedDiagnosticsSpecificOptions[diagnostic.Id] = ReportDiagnostic.Default; + } + else + { + supportedDiagnosticsSpecificOptions[diagnostic.Id] = diagnostic.DefaultSeverity switch + { + DiagnosticSeverity.Hidden => ReportDiagnostic.Hidden, + DiagnosticSeverity.Info => ReportDiagnostic.Info, + DiagnosticSeverity.Warning => ReportDiagnostic.Warn, + DiagnosticSeverity.Error => ReportDiagnostic.Error, + _ => throw new InvalidOperationException(), + }; + } + } + } + + // Report exceptions during the analysis process as errors + supportedDiagnosticsSpecificOptions.Add("AD0001", ReportDiagnostic.Error); + + foreach (var id in DisabledDiagnostics) + { + supportedDiagnosticsSpecificOptions[id] = ReportDiagnostic.Suppress; + } + + // update the project compilation options + var modifiedSpecificDiagnosticOptions = supportedDiagnosticsSpecificOptions.ToImmutableDictionary().SetItems(project.CompilationOptions.SpecificDiagnosticOptions); + var modifiedCompilationOptions = project.CompilationOptions.WithSpecificDiagnosticOptions(modifiedSpecificDiagnosticOptions); + + var solution = project.Solution.WithProjectCompilationOptions(project.Id, modifiedCompilationOptions); + return solution.GetProject(project.Id); + } + + public Workspace CreateWorkspace() + { + var workspace = CreateWorkspaceImpl(); + _workspaces.Add(workspace); + return workspace; + } + + protected virtual Workspace CreateWorkspaceImpl() + { + var exportProvider = ExportProviderFactory.Value.CreateExportProvider(); + var host = MefHostServices.Create(exportProvider.AsCompositionContext()); + return new AdhocWorkspace(host); + } + + protected abstract CompilationOptions CreateCompilationOptions(); + + protected abstract ParseOptions CreateParseOptions(); + + /// + /// Sort s by location in source document. + /// + /// A collection of s to be sorted. + /// A collection containing the input , sorted by + /// and . + private static (Project project, Diagnostic diagnostic)[] SortDistinctDiagnostics(IEnumerable<(Project project, Diagnostic diagnostic)> diagnostics) + { + return diagnostics + .OrderBy(d => d.diagnostic.Location.GetLineSpan().Path, StringComparer.Ordinal) + .ThenBy(d => d.diagnostic.Location.SourceSpan.Start) + .ThenBy(d => d.diagnostic.Location.SourceSpan.End) + .ThenBy(d => d.diagnostic.Id) + .ThenBy(d => GetArguments(d.diagnostic), LexicographicComparer.Instance).ToArray(); + } + + private static IReadOnlyList GetArguments(Diagnostic diagnostic) + { + return (IReadOnlyList?)diagnostic.GetType().GetProperty("Arguments", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(diagnostic) + ?? new object[0]; + } + + /// + /// Gets the analyzers being tested. + /// + /// + /// New instances of all the analyzers being tested. + /// + protected abstract IEnumerable GetDiagnosticAnalyzers(); + + private sealed class LexicographicComparer : IComparer?> + { + public static LexicographicComparer Instance { get; } = new LexicographicComparer(); + + public int Compare(IEnumerable? x, IEnumerable? y) + { + if (x is null) + { + return y is null ? 0 : -1; + } + else if (y is null) + { + return 1; + } + + using var xe = x.GetEnumerator(); + using var ye = y.GetEnumerator(); + + while (xe.MoveNext()) + { + if (!ye.MoveNext()) + { + // y has fewer elements + return 1; + } + + IComparer elementComparer = Comparer.Default; + if (xe.Current is string && ye.Current is string) + { + // Avoid culture-sensitive string comparisons + elementComparer = StringComparer.Ordinal; + } + + try + { + var elementComparison = elementComparer.Compare(xe.Current, ye.Current); + if (elementComparison == 0) + { + continue; + } + + return elementComparison; + } + catch (ArgumentException) + { + // The arguments are not directly comparable, so convert the values to strings and try again + var elementComparison = string.CompareOrdinal(xe.Current?.ToString(), ye.Current?.ToString()); + if (elementComparison == 0) + { + continue; + } + + return elementComparison; + } + } + + if (ye.MoveNext()) + { + // x has fewer elements + return -1; + } + + return 0; + } + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/AnalyzerVerifier`3.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/AnalyzerVerifier`3.cs new file mode 100644 index 000000000..8a835077a --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/AnalyzerVerifier`3.cs @@ -0,0 +1,101 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// A default verifier for diagnostic analyzers. + /// + /// The to test. + /// The test implementation to use. + /// The type of verifier to use. + public class AnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TTest : AnalyzerTest, new() + where TVerifier : IVerifier, new() + { + /// + /// Creates a representing an expected diagnostic for the single + /// supported by the analyzer. + /// + /// A initialized using the single descriptor supported by the analyzer. + /// + /// If the analyzer declares support for more than one diagnostic descriptor. + /// -or- + /// If the analyzer does not declare support for any diagnostic descriptors. + /// + public static DiagnosticResult Diagnostic() + { + var analyzer = new TAnalyzer(); + try + { + return Diagnostic(analyzer.SupportedDiagnostics.Single()); + } + catch (InvalidOperationException ex) + { + throw new InvalidOperationException( + $"'{nameof(Diagnostic)}()' can only be used when the analyzer has a single supported diagnostic. Use the '{nameof(Diagnostic)}(DiagnosticDescriptor)' overload to specify the descriptor from which to create the expected result.", + ex); + } + } + + /// + /// Creates a representing an expected diagnostic for the single + /// with the specified ID supported by the analyzer. + /// + /// The expected diagnostic ID. + /// A initialized using the single descriptor with the specified ID supported by the analyzer. + /// + /// If the analyzer declares support for more than one diagnostic descriptor with the specified ID. + /// -or- + /// If the analyzer does not declare support for any diagnostic descriptors with the specified ID. + /// + public static DiagnosticResult Diagnostic(string diagnosticId) + { + var analyzer = new TAnalyzer(); + try + { + return Diagnostic(analyzer.SupportedDiagnostics.Single(i => i.Id == diagnosticId)); + } + catch (InvalidOperationException ex) + { + throw new InvalidOperationException( + $"'{nameof(Diagnostic)}(string)' can only be used when the analyzer has a single supported diagnostic with the specified ID. Use the '{nameof(Diagnostic)}(DiagnosticDescriptor)' overload to specify the descriptor from which to create the expected result.", + ex); + } + } + + /// + /// Creates a representing an expected diagnostic for the specified + /// . + /// + /// The diagnostic descriptor. + /// A initialed using the specified . + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) => new DiagnosticResult(descriptor); + + /// + /// Verifies the analyzer produces the specified diagnostics for the given source text. + /// + /// The source text to test, which may include markup syntax. + /// The expected diagnostics. These diagnostics are in addition to any diagnostics + /// defined in markup. + /// A representing the asynchronous operation. + public static Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new TTest + { + TestCode = source, + }; + + test.ExpectedDiagnostics.AddRange(expected); + return test.RunAsync(CancellationToken.None); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/CodeActionTest`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/CodeActionTest`1.cs new file mode 100644 index 000000000..e3542db49 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/CodeActionTest`1.cs @@ -0,0 +1,485 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Testing +{ + public abstract class CodeActionTest : AnalyzerTest + where TVerifier : IVerifier, new() + { + /// + /// Gets or sets the index of the code action to apply. + /// + /// + /// If and are both specified, the + /// test will further verify that the two properties refer to the same code action. + /// + /// + public int? CodeActionIndex { get; set; } + + /// + /// Gets or sets the of the code action to apply. + /// + /// + /// If and are both specified, the + /// test will further verify that the two properties refer to the same code action. + /// + /// + public string? CodeActionEquivalenceKey { get; set; } + + /// + /// Gets or sets an additional verifier for a . After the code action is selected, it is + /// passed to this verification method to test any other properties of the code action. + /// + /// + /// For a successful test, the verification action is expected to complete without throwing an + /// exception. + /// + public Action? CodeActionVerifier { get; set; } + + /// + /// Gets or sets the validation mode for code actions. The default is + /// . + /// + public CodeActionValidationMode CodeActionValidationMode { get; set; } = CodeActionValidationMode.SemanticStructure; + + /// + /// Gets the syntax kind enumeration type for the current code action test. + /// + public abstract Type SyntaxKindType { get; } + + protected static bool CodeActionExpected(SolutionState state) + { + return state.InheritanceMode != null + || state.MarkupHandling != null + || state.Sources.Any() + || state.GeneratedSources.Any() + || state.AdditionalFiles.Any() + || state.AnalyzerConfigFiles.Any() + || state.AdditionalFilesFactories.Any(); + } + + protected static bool HasAnyChange(ProjectState oldState, ProjectState newState, bool recursive) + { + if (!oldState.Sources.SequenceEqual(newState.Sources, SourceFileEqualityComparer.Instance) + || !oldState.GeneratedSources.SequenceEqual(newState.GeneratedSources, SourceFileEqualityComparer.Instance) + || !oldState.AdditionalFiles.SequenceEqual(newState.AdditionalFiles, SourceFileEqualityComparer.Instance) + || !oldState.AnalyzerConfigFiles.SequenceEqual(newState.AnalyzerConfigFiles, SourceFileEqualityComparer.Instance)) + { + return true; + } + + if (!recursive) + { + return false; + } + + if (oldState is SolutionState oldSolutionState) + { + if (!(newState is SolutionState newSolutionState)) + { + throw new ArgumentException("Unexpected mismatch of SolutionState with ProjectState."); + } + + if (oldSolutionState.AdditionalProjects.Count != newSolutionState.AdditionalProjects.Count) + { + return true; + } + + foreach (var oldAdditionalState in oldSolutionState.AdditionalProjects) + { + if (!newSolutionState.AdditionalProjects.TryGetValue(oldAdditionalState.Key, out var newAdditionalState) + || HasAnyChange(oldAdditionalState.Value, newAdditionalState, recursive: true)) + { + return true; + } + } + } + + return false; + } + + protected static CodeAction? TryGetCodeActionToApply(int iteration, ImmutableArray actions, int? codeActionIndex, string? codeActionEquivalenceKey, Action? codeActionVerifier, IVerifier verifier) + { + CodeAction? result; + if (codeActionIndex.HasValue && codeActionEquivalenceKey != null) + { + var expectedAction = actions.FirstOrDefault(action => action.EquivalenceKey == codeActionEquivalenceKey); + if (expectedAction is null && iteration > 0) + { + // No matching code action was found. This is acceptable if this is not the first iteration. + return null; + } + + verifier.True( + actions.Length > codeActionIndex, + $"Expected to find a code action at index '{codeActionIndex}', but only '{actions.Length}' code actions were found."); + + verifier.Equal( + codeActionEquivalenceKey, + actions[codeActionIndex.Value].EquivalenceKey, + "The code action equivalence key and index must be consistent when both are specified."); + + result = actions[codeActionIndex.Value]; + } + else if (codeActionEquivalenceKey != null) + { + result = actions.FirstOrDefault(x => x.EquivalenceKey == codeActionEquivalenceKey); + } + else if (actions.Length > (codeActionIndex ?? 0)) + { + result = actions[codeActionIndex ?? 0]; + } + else + { + return null; + } + + if (result is object) + { + codeActionVerifier?.Invoke(result, verifier); + } + + return result; + } + + protected virtual ImmutableArray FilterCodeActions(ImmutableArray actions) + { + var builder = actions.ToBuilder(); + while (true) + { + var changesMade = false; + for (var i = builder.Count - 1; i >= 0; i--) + { + var action = builder[i]; + var nestedActions = action.GetNestedActions(); + if (!nestedActions.IsEmpty) + { + builder.RemoveAt(i); + for (var j = nestedActions.Length - 1; j >= 0; j--) + { + builder.Insert(i, nestedActions[j]); + } + + changesMade = true; + } + } + + if (!changesMade) + { + break; + } + } + + return builder.ToImmutable(); + } + + /// + /// Apply the inputted to the inputted document. + /// Meant to be used to apply code fixes. + /// + /// The to apply the code action on. + /// A that will be applied to the + /// . + /// The verifier to use for test assertions. + /// The that the task will observe. + /// A with the changes from the . + protected async Task ApplyCodeActionAsync(Project project, CodeAction codeAction, IVerifier verifier, CancellationToken cancellationToken) + { + var operations = await codeAction.GetOperationsAsync(cancellationToken).ConfigureAwait(false); + var solution = operations.OfType().Single().ChangedSolution; + var changedProject = solution.GetProject(project.Id); + if (changedProject != project) + { + project = await RecreateProjectDocumentsAsync(changedProject, verifier, cancellationToken).ConfigureAwait(false); + } + + return project; + } + + /// + /// Implements a workaround for issue #936, force re-parsing to get the same sort of syntax tree as the original document. + /// + /// The project to update. + /// The verifier to use for test assertions. + /// The . + /// The updated . + private async Task RecreateProjectDocumentsAsync(Project project, IVerifier verifier, CancellationToken cancellationToken) + { + foreach (var documentId in project.DocumentIds) + { + var document = project.GetDocument(documentId); + var initialTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + document = await RecreateDocumentAsync(document, cancellationToken).ConfigureAwait(false); + var recreatedTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + if (CodeActionValidationMode != CodeActionValidationMode.None) + { + try + { + // We expect the tree produced by the code fix (initialTree) to match the form of the tree produced + // by the compiler for the same text (recreatedTree). + TreeEqualityVisitor.AssertNodesEqual( + verifier, + SyntaxKindType, + await recreatedTree.GetRootAsync(cancellationToken).ConfigureAwait(false), + await initialTree.GetRootAsync(cancellationToken).ConfigureAwait(false), + checkTrivia: CodeActionValidationMode == CodeActionValidationMode.Full); + } + catch + { + // Try to revalidate the tree with a better message + var renderedInitialTree = TreeToString(await initialTree.GetRootAsync(cancellationToken).ConfigureAwait(false), CodeActionValidationMode); + var renderedRecreatedTree = TreeToString(await recreatedTree.GetRootAsync(cancellationToken).ConfigureAwait(false), CodeActionValidationMode); + verifier.EqualOrDiff(renderedRecreatedTree, renderedInitialTree); + + // This is not expected to be hit, but it will be hit if the validation failure occurred in a + // portion of the tree not captured by the rendered form from TreeToString. + throw; + } + } + + project = document.Project; + } + + return project; + } + + private static async Task RecreateDocumentAsync(Document document, CancellationToken cancellationToken) + { + var newText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + return document.WithText(SourceText.From(newText.ToString(), newText.Encoding, newText.ChecksumAlgorithm)); + } + + private string TreeToString(SyntaxNodeOrToken syntaxNodeOrToken, CodeActionValidationMode validationMode) + { + var result = new StringBuilder(); + TreeToString(syntaxNodeOrToken, string.Empty, validationMode, result); + return result.ToString(); + } + + private void TreeToString(SyntaxNodeOrToken syntaxNodeOrToken, string indent, CodeActionValidationMode validationMode, StringBuilder result) + { + if (syntaxNodeOrToken.IsNode) + { + result.AppendLine($"{indent}Node({Kind(syntaxNodeOrToken.RawKind)}):"); + + var childIndent = indent + " "; + foreach (var child in syntaxNodeOrToken.ChildNodesAndTokens()) + { + TreeToString(child, childIndent, validationMode, result); + } + } + else + { + var syntaxToken = syntaxNodeOrToken.AsToken(); + result.AppendLine($"{indent}Token({Kind(syntaxToken.RawKind)}): {Escape(syntaxToken.Text)}"); + + if (validationMode == CodeActionValidationMode.Full) + { + var childIndent = indent + " "; + foreach (var trivia in syntaxToken.LeadingTrivia) + { + if (trivia.HasStructure) + { + result.AppendLine($"{childIndent}Leading({Kind(trivia.RawKind)}):"); + TreeToString(trivia.GetStructure(), childIndent + " ", validationMode, result); + } + else + { + result.AppendLine($"{childIndent}Leading({Kind(trivia.RawKind)}): {Escape(trivia.ToString())}"); + } + } + + foreach (var trivia in syntaxToken.TrailingTrivia) + { + if (trivia.HasStructure) + { + result.AppendLine($"{childIndent}Trailing({Kind(trivia.RawKind)}):"); + TreeToString(trivia.GetStructure(), childIndent + " ", validationMode, result); + } + else + { + result.AppendLine($"{childIndent}Trailing({Kind(trivia.RawKind)}): {Escape(trivia.ToString())}"); + } + } + } + } + + // Local functions + string Escape(string text) + { + return text + .Replace("\\", "\\\\") + .Replace("\t", "\\t") + .Replace("\r", "\\r") + .Replace("\n", "\\n"); + } + + string Kind(int syntaxKind) + { + if (SyntaxKindType.GetTypeInfo()?.IsEnum ?? false) + { + return Enum.Format(SyntaxKindType, (ushort)syntaxKind, "G"); + } + else + { + return syntaxKind.ToString(); + } + } + } + + private sealed class SourceFileEqualityComparer : IEqualityComparer<(string filename, SourceText content)> + { + private SourceFileEqualityComparer() + { + } + + public static SourceFileEqualityComparer Instance { get; } = new SourceFileEqualityComparer(); + + public bool Equals((string filename, SourceText content) x, (string filename, SourceText content) y) + { + if (x.filename != y.filename) + { + return false; + } + + if (x.content is null || y.content is null) + { + return ReferenceEquals(x, y); + } + + return x.content.Encoding == y.content.Encoding + && x.content.ChecksumAlgorithm == y.content.ChecksumAlgorithm + && x.content.ContentEquals(y.content); + } + + public int GetHashCode((string filename, SourceText content) obj) + { + return obj.filename.GetHashCode() + ^ (obj.content?.ToString().GetHashCode() ?? 0); + } + } + + private class TreeEqualityVisitor + { + private readonly IVerifier _verifier; + private readonly Type _syntaxKindType; + private readonly SyntaxNode _expected; + private readonly bool _checkTrivia; + + private TreeEqualityVisitor(IVerifier verifier, Type syntaxKindType, SyntaxNode expected, bool checkTrivia) + { + _verifier = verifier; + _syntaxKindType = syntaxKindType; + _expected = expected ?? throw new ArgumentNullException(nameof(expected)); + _checkTrivia = checkTrivia; + } + + public void Visit(SyntaxNode node) + { + AssertSyntaxKindEqual(_expected.RawKind, node.RawKind); + AssertChildSyntaxListEqual(_expected.ChildNodesAndTokens(), node.ChildNodesAndTokens(), _checkTrivia); + } + + internal static void AssertNodesEqual(IVerifier verifier, Type syntaxKindType, SyntaxNode expected, SyntaxNode actual, bool checkTrivia) + { + new TreeEqualityVisitor(verifier, syntaxKindType, expected, checkTrivia).Visit(actual); + } + + private void AssertNodesEqual(SyntaxNode expected, SyntaxNode actual, bool checkTrivia) + { + AssertNodesEqual(_verifier, _syntaxKindType, expected, actual, checkTrivia); + } + + private void AssertChildSyntaxListEqual(ChildSyntaxList expected, ChildSyntaxList actual, bool checkTrivia) + { + _verifier.Equal(expected.Count, actual.Count); + foreach (var (expectedChild, actualChild) in expected.Zip(actual, (first, second) => (first, second))) + { + if (expectedChild.IsToken) + { + _verifier.True(actualChild.IsToken); + AssertTokensEqual(expectedChild.AsToken(), actualChild.AsToken(), checkTrivia); + } + else + { + _verifier.True(actualChild.IsNode); + AssertNodesEqual(expectedChild.AsNode(), actualChild.AsNode(), checkTrivia); + } + } + } + + private void AssertTokensEqual(SyntaxToken expected, SyntaxToken actual, bool checkTrivia) + { + AssertTriviaListEqual(expected.LeadingTrivia, actual.LeadingTrivia, checkTrivia); + AssertSyntaxKindEqual(expected.RawKind, actual.RawKind); + _verifier.Equal(expected.Value, actual.Value); + _verifier.Equal(expected.Text, actual.Text); + _verifier.Equal(expected.ValueText, actual.ValueText); + AssertTriviaListEqual(expected.TrailingTrivia, actual.TrailingTrivia, checkTrivia); + } + + private void AssertTriviaListEqual(SyntaxTriviaList expected, SyntaxTriviaList actual, bool checkTrivia) + { + if (!checkTrivia) + { + return; + } + + for (var i = 0; i < Math.Min(expected.Count, actual.Count); i++) + { + AssertTriviaEqual(expected[i], actual[i], checkTrivia); + } + + _verifier.Equal(expected.Count, actual.Count); + } + + private void AssertTriviaEqual(SyntaxTrivia expected, SyntaxTrivia actual, bool checkTrivia) + { + if (!checkTrivia) + { + return; + } + + AssertSyntaxKindEqual(expected.RawKind, actual.RawKind); + _verifier.Equal(expected.HasStructure, actual.HasStructure); + _verifier.Equal(expected.IsDirective, actual.IsDirective); + _verifier.Equal(expected.GetAnnotations(), actual.GetAnnotations()); + if (expected.HasStructure) + { + AssertNodesEqual(expected.GetStructure(), actual.GetStructure(), checkTrivia); + } + } + + private void AssertSyntaxKindEqual(int expected, int actual) + { + if (expected == actual) + { + return; + } + + if (_syntaxKindType.GetTypeInfo()?.IsEnum ?? false) + { + _verifier.Equal( + Enum.Format(_syntaxKindType, (ushort)expected, "G"), + Enum.Format(_syntaxKindType, (ushort)actual, "G")); + } + else + { + _verifier.Equal(expected, actual); + } + } + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/CodeActionValidationMode.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/CodeActionValidationMode.cs new file mode 100644 index 000000000..40f5410e8 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/CodeActionValidationMode.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Specifies the validation mode for code actions. + /// + public enum CodeActionValidationMode + { + /// + /// Code action verification is limited to the raw text produced by the action. + /// + None, + + /// + /// Code action verification ensures that semantic structure of the tree produced by the code action matches the + /// form produced by the compiler when parsing the text form of the document. Differences in trivia nodes, in + /// particular the associativity of to leading or trailing trivia lists, is ignored. + /// + /// + /// Code actions are generally expected to adhere to this validation mode. + /// + SemanticStructure, + + /// + /// Code action verification ensures that the tree produced by a code action exactly matches the form of the + /// tree produced by the compiler when parsing the text representation of the tree. + /// + Full, + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/CompilerDiagnostics.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/CompilerDiagnostics.cs new file mode 100644 index 000000000..729c89f39 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/CompilerDiagnostics.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Specifies the behavior of compiler diagnostics in validation scenarios. + /// + public enum CompilerDiagnostics + { + /// + /// All compiler diagnostics are ignored. + /// + None, + + /// + /// Compiler errors are included in verification. + /// + Errors, + + /// + /// Compiler errors and warnings are included in verification. + /// + Warnings, + + /// + /// Compiler errors, warnings, and suggestions are included in verification. + /// + Suggestions, + + /// + /// All compiler diagnostics are included in verification. + /// + All, + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/DefaultVerifier.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/DefaultVerifier.cs new file mode 100644 index 000000000..73b081fcb --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/DefaultVerifier.cs @@ -0,0 +1,182 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Provides a default implementation of . + /// + /// + /// This verifier is not dependent on any particular test framework. Each verification method throws + /// on failure. + /// + public class DefaultVerifier : IVerifier + { + /// + /// Initializes a new instance of the class. + /// + public DefaultVerifier() + : this(ImmutableStack.Empty) + { + } + + /// + /// Initializes a new instance of the class with the specified context. + /// + /// The verification context, with the innermost verification context label at the top of + /// the stack. + /// If is . + protected DefaultVerifier(ImmutableStack context) + { + Context = context ?? throw new ArgumentNullException(nameof(context)); + } + + /// + /// Gets the current verification context. The innermost verification context label is the top item on the + /// stack. + /// + protected ImmutableStack Context { get; } + + /// + public virtual void Empty(string collectionName, IEnumerable collection) + { + if (collection?.Any() == true) + { + throw new InvalidOperationException(CreateMessage($"'{collectionName}' is not empty")); + } + } + + /// + public virtual void NotEmpty(string collectionName, IEnumerable collection) + { + if (collection?.Any() == false) + { + throw new InvalidOperationException(CreateMessage($"'{collectionName}' is empty")); + } + } + + /// + public virtual void LanguageIsSupported(string language) + { + if (language != LanguageNames.CSharp && language != LanguageNames.VisualBasic) + { + throw new InvalidOperationException(CreateMessage($"Unsupported Language: '{language}'")); + } + } + + /// + public virtual void Equal(T expected, T actual, string? message = null) + { + if (!EqualityComparer.Default.Equals(expected, actual)) + { + throw new InvalidOperationException(CreateMessage(message ?? $"items not equal. expected:'{expected}' actual:'{actual}'")); + } + } + + /// + public virtual void True([DoesNotReturnIf(false)] bool assert, string? message = null) + { + if (!assert) + { + throw new InvalidOperationException(CreateMessage(message ?? $"Expected value to be 'true' but was 'false'")); + } + } + + /// + public virtual void False([DoesNotReturnIf(true)] bool assert, string? message = null) + { + if (assert) + { + throw new InvalidOperationException(CreateMessage(message ?? $"Expected value to be 'false' but was 'true'")); + } + } + + /// + [DoesNotReturn] + public virtual void Fail(string? message = null) + { + throw new InvalidOperationException(CreateMessage(message ?? "Verification failed for an unspecified reason.")); + } + + /// + public virtual void SequenceEqual(IEnumerable expected, IEnumerable actual, IEqualityComparer? equalityComparer = null, string? message = null) + { + var comparer = new SequenceEqualEnumerableEqualityComparer(equalityComparer); + var areEqual = comparer.Equals(expected, actual); + if (!areEqual) + { + throw new InvalidOperationException(CreateMessage(message ?? $"Sequences are not equal")); + } + } + + /// + public virtual IVerifier PushContext(string context) + { + if (GetType() != typeof(DefaultVerifier)) + { + throw new InvalidOperationException($"Custom verifier types must override {nameof(PushContext)}"); + } + + return new DefaultVerifier(Context.Push(context)); + } + + /// + /// Creates a full message for a verifier failure combining the current verification with + /// the for the current verification. + /// + /// The failure message to report. + /// A full failure message containing both the verification context and the failure message for the current test. + protected virtual string CreateMessage(string message) + { + foreach (var frame in Context) + { + message = "Context: " + frame + Environment.NewLine + message; + } + + return message; + } + + private sealed class SequenceEqualEnumerableEqualityComparer : IEqualityComparer?> + { + private readonly IEqualityComparer _itemEqualityComparer; + + public SequenceEqualEnumerableEqualityComparer(IEqualityComparer? itemEqualityComparer) + { + _itemEqualityComparer = itemEqualityComparer ?? EqualityComparer.Default; + } + + public bool Equals(IEnumerable? x, IEnumerable? y) + { + if (ReferenceEquals(x, y)) { return true; } + if (x is null || y is null) { return false; } + + return x.SequenceEqual(y, _itemEqualityComparer); + } + + public int GetHashCode(IEnumerable? obj) + { + if (obj is null) + { + return 0; + } + + // From System.Tuple + // + // The suppression is required due to an invalid contract in IEqualityComparer + // https://github.com/dotnet/runtime/issues/30998 + return obj + .Select(item => _itemEqualityComparer.GetHashCode(item!)) + .Aggregate( + 0, + (aggHash, nextHash) => ((aggHash << 5) + aggHash) ^ nextHash); + } + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/DiagnosticLocation.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/DiagnosticLocation.cs new file mode 100644 index 000000000..b06d3befe --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/DiagnosticLocation.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Represents an expected appearing in or + /// . + /// + public readonly struct DiagnosticLocation + { + /// + /// Initializes a new instance of the structure with the specified location and + /// options. + /// + /// The location of the diagnostic. + /// The options to consider when validating this location. + public DiagnosticLocation(FileLinePositionSpan span, DiagnosticLocationOptions options) + { + Span = span; + Options = options; + } + + /// + /// Gets the file and span of the location. + /// + public FileLinePositionSpan Span { get; } + + /// + /// Gets the options for validating the location. + /// + public DiagnosticLocationOptions Options { get; } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/DiagnosticLocationOptions.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/DiagnosticLocationOptions.cs new file mode 100644 index 000000000..fb337a621 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/DiagnosticLocationOptions.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Defines options for interpreting . + /// + [Flags] + public enum DiagnosticLocationOptions + { + /// + /// The diagnostic location is a simple . + /// + None = 0, + + /// + /// The diagnostic location is defined as a position instead of a span. The length of the actual diagnostic span + /// should be ignored when comparing results. + /// + IgnoreLength = 1, + + /// + /// The diagnostic location is defined in markup. The associated has the + /// following characteristics: + /// + /// + /// The is an empty string. + /// The is 0. + /// The is the index of the markup span which defines + /// the location. For example, an index of 5 would appear using the markup syntax {|#5:...|} or + /// {|#5:...|#5}. + /// + /// + InterpretAsMarkupKey = 2, + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/DiagnosticOptions.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/DiagnosticOptions.cs new file mode 100644 index 000000000..28199b3fb --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/DiagnosticOptions.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Defines options for interpreting . + /// + [Flags] + public enum DiagnosticOptions + { + /// + /// The result should be interpreted using the default settings. + /// + None = 0, + + /// + /// The primary diagnostic location is defined, but additional locations have not been provided. Disables + /// validation of additional locations reported for the corresponding diagnostics. + /// + IgnoreAdditionalLocations = 1, + + /// + /// Ignore the diagnostic severity when verifying this diagnostic result. + /// + IgnoreSeverity = 2, + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/DiagnosticResult.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/DiagnosticResult.cs new file mode 100644 index 000000000..aac04fe91 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/DiagnosticResult.cs @@ -0,0 +1,447 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Structure that stores information about a appearing in a source. + /// + public readonly struct DiagnosticResult + { + public static readonly DiagnosticResult[] EmptyDiagnosticResults = { }; + + private static readonly object[] EmptyArguments = new object[0]; + + private readonly ImmutableArray _spans; + private readonly bool _suppressMessage; + private readonly string? _message; + + /// + /// Initializes a new instance of the structure with the specified + /// and . + /// + /// The diagnostic ID. + /// The diagnostic severity. + public DiagnosticResult(string id, DiagnosticSeverity severity) + : this() + { + Id = id; + Severity = severity; + } + + /// + /// Initializes a new instance of the structure with the , + /// , and taken from the specified + /// . + /// + /// The diagnostic descriptor. + public DiagnosticResult(DiagnosticDescriptor descriptor) + : this() + { + Id = descriptor.Id; + Severity = descriptor.DefaultSeverity; + MessageFormat = descriptor.MessageFormat; + } + + private DiagnosticResult( + ImmutableArray spans, + bool suppressMessage, + string? message, + DiagnosticSeverity severity, + DiagnosticOptions options, + string id, + LocalizableString? messageFormat, + object?[]? messageArguments) + { + _spans = spans; + _suppressMessage = suppressMessage; + _message = message; + Severity = severity; + Options = options; + Id = id; + MessageFormat = messageFormat; + MessageArguments = messageArguments; + } + + /// + /// Gets the locations where the expected diagnostic is reported. + /// + /// An empty array is returned for no-location diagnostics. + /// The first location corresponds to . + /// Remaining locations correspond to . These + /// locations are not validated if the diagnostic has the + /// flag set. + /// + /// + public ImmutableArray Spans => _spans.IsDefault ? ImmutableArray.Empty : _spans; + + /// + /// Gets the expected severity of the diagnostic. + /// + public DiagnosticSeverity Severity { get; } + + /// + /// Gets the options to consider during validation of the expected diagnostic. The default value is + /// . + /// + public DiagnosticOptions Options { get; } + + /// + /// Gets the expected ID of the diagnostic. + /// + public string Id { get; } + + /// + /// Gets the expected message of the diagnostic, if any. + /// + /// + /// The expected message for the diagnostic; otherwise, if the message should not be + /// validated. + /// + public string? Message + { + get + { + if (_suppressMessage) + { + return null; + } + + if (_message != null) + { + return _message; + } + + if (MessageFormat != null) + { + try + { + return string.Format(MessageFormat.ToString(), MessageArguments ?? EmptyArguments); + } + catch (FormatException) + { + return MessageFormat.ToString(); + } + } + + return null; + } + } + + /// + /// Gets the expected message format for the diagnostic. + /// + public LocalizableString? MessageFormat { get; } + + /// + /// Gets the expected message arguments for the diagnostic. These arguments are used for formatting + /// when has not be set directly. + /// + public object?[]? MessageArguments { get; } + + /// + /// Gets a value indicating whether the diagnostic is expected to have a location. + /// + /// + /// if the diagnostic is expected to have a location; otherwise, + /// if a no-locatino diagnostic is expected. + /// + public bool HasLocation => !Spans.IsEmpty; + + /// + /// Creates a for a compiler error with the specified ID. + /// + /// The compiler error ID. + /// A for a compiler error with the specified ID. + public static DiagnosticResult CompilerError(string identifier) + => new DiagnosticResult(identifier, DiagnosticSeverity.Error); + + /// + /// Creates a for a compiler warning with the specified ID. + /// + /// The compiler warning ID. + /// A for a compiler warning with the specified ID. + public static DiagnosticResult CompilerWarning(string identifier) + => new DiagnosticResult(identifier, DiagnosticSeverity.Warning); + + /// + /// Transforms the current to have the specified . + /// + /// The expected diagnostic severity. + /// A new copied from the current instance with the specified + /// applied. + public DiagnosticResult WithSeverity(DiagnosticSeverity severity) + { + return new DiagnosticResult( + spans: _spans, + suppressMessage: _suppressMessage, + message: _message, + severity: severity, + options: Options, + id: Id, + messageFormat: MessageFormat, + messageArguments: MessageArguments); + } + + /// + /// Transforms the current to have the specified . + /// + /// The options to consider during validation of the expected diagnostic. + /// A new copied from the current instance with the specified + /// applied. + public DiagnosticResult WithOptions(DiagnosticOptions options) + { + return new DiagnosticResult( + spans: _spans, + suppressMessage: _suppressMessage, + message: _message, + severity: Severity, + options: options, + id: Id, + messageFormat: MessageFormat, + messageArguments: MessageArguments); + } + + public DiagnosticResult WithArguments(params object[] arguments) + { + return new DiagnosticResult( + spans: _spans, + suppressMessage: _suppressMessage, + message: _message, + severity: Severity, + options: Options, + id: Id, + messageFormat: MessageFormat, + messageArguments: arguments); + } + + public DiagnosticResult WithMessage(string? message) + { + return new DiagnosticResult( + spans: _spans, + suppressMessage: message is null, + message: message, + severity: Severity, + options: Options, + id: Id, + messageFormat: MessageFormat, + messageArguments: MessageArguments); + } + + public DiagnosticResult WithMessageFormat(LocalizableString messageFormat) + { + return new DiagnosticResult( + spans: _spans, + suppressMessage: _suppressMessage, + message: _message, + severity: Severity, + options: Options, + id: Id, + messageFormat: messageFormat, + messageArguments: MessageArguments); + } + + public DiagnosticResult WithNoLocation() + { + return new DiagnosticResult( + spans: ImmutableArray.Empty, + suppressMessage: _suppressMessage, + message: _message, + severity: Severity, + options: Options, + id: Id, + messageFormat: MessageFormat, + messageArguments: MessageArguments); + } + + public DiagnosticResult WithLocation(int line, int column) + => WithLocation(path: string.Empty, new LinePosition(line - 1, column - 1)); + + public DiagnosticResult WithLocation(LinePosition location) + => WithLocation(path: string.Empty, location); + + public DiagnosticResult WithLocation(string path, int line, int column) + => WithLocation(path, new LinePosition(line - 1, column - 1)); + + public DiagnosticResult WithLocation(string path, LinePosition location) + => AppendSpan(new FileLinePositionSpan(path, location, location), DiagnosticLocationOptions.IgnoreLength); + + public DiagnosticResult WithLocation(string path, LinePosition location, DiagnosticLocationOptions options) + => AppendSpan(new FileLinePositionSpan(path, location, location), options | DiagnosticLocationOptions.IgnoreLength); + + public DiagnosticResult WithSpan(int startLine, int startColumn, int endLine, int endColumn) + => WithSpan(path: string.Empty, startLine, startColumn, endLine, endColumn); + + public DiagnosticResult WithSpan(string path, int startLine, int startColumn, int endLine, int endColumn) + => AppendSpan(new FileLinePositionSpan(path, new LinePosition(startLine - 1, startColumn - 1), new LinePosition(endLine - 1, endColumn - 1)), DiagnosticLocationOptions.None); + + public DiagnosticResult WithSpan(FileLinePositionSpan span) + => AppendSpan(span, DiagnosticLocationOptions.None); + + public DiagnosticResult WithSpan(FileLinePositionSpan span, DiagnosticLocationOptions options) + => AppendSpan(span, options); + + public DiagnosticResult WithLocation(int markupKey) + => AppendSpan(new FileLinePositionSpan(string.Empty, new LinePosition(0, markupKey), new LinePosition(0, markupKey)), DiagnosticLocationOptions.InterpretAsMarkupKey); + + public DiagnosticResult WithLocation(int markupKey, DiagnosticLocationOptions options) + => AppendSpan(new FileLinePositionSpan(string.Empty, new LinePosition(0, markupKey), new LinePosition(0, markupKey)), options | DiagnosticLocationOptions.InterpretAsMarkupKey); + + public DiagnosticResult WithDefaultPath(string path) + { + if (Spans.IsEmpty) + { + return this; + } + + var spans = Spans.ToBuilder(); + for (var i = 0; i < spans.Count; i++) + { + if (spans[i].Options.HasFlag(DiagnosticLocationOptions.InterpretAsMarkupKey)) + { + // Markup keys have a predefined syntax that requires empty paths. + continue; + } + + if (spans[i].Span.Path == string.Empty) + { + spans[i] = new DiagnosticLocation(new FileLinePositionSpan(path, spans[i].Span.Span), spans[i].Options); + } + } + + return new DiagnosticResult( + spans: spans.MoveToImmutable(), + suppressMessage: _suppressMessage, + message: _message, + severity: Severity, + options: Options, + id: Id, + messageFormat: MessageFormat, + messageArguments: MessageArguments); + } + + internal DiagnosticResult WithAppliedMarkupLocations(ImmutableDictionary markupLocations) + { + if (Spans.IsEmpty) + { + return this; + } + + var verifier = new DefaultVerifier(); + var spans = Spans.ToBuilder(); + for (var i = 0; i < spans.Count; i++) + { + if (!spans[i].Options.HasFlag(DiagnosticLocationOptions.InterpretAsMarkupKey)) + { + continue; + } + + var index = spans[i].Span.StartLinePosition.Character; + var expected = new FileLinePositionSpan(path: string.Empty, new LinePosition(0, index), new LinePosition(0, index)); + if (!spans[i].Span.Equals(expected)) + { + verifier.Equal(expected, spans[i].Span); + } + + if (!markupLocations.TryGetValue("#" + index, out var location)) + { + throw new InvalidOperationException($"The markup location '#{index}' was not found in the input."); + } + + spans[i] = new DiagnosticLocation(location, spans[i].Options & ~DiagnosticLocationOptions.InterpretAsMarkupKey); + } + + return new DiagnosticResult( + spans: spans.MoveToImmutable(), + suppressMessage: _suppressMessage, + message: _message, + severity: Severity, + options: Options, + id: Id, + messageFormat: MessageFormat, + messageArguments: MessageArguments); + } + + public DiagnosticResult WithLineOffset(int offset) + { + if (Spans.IsEmpty) + { + return this; + } + + var result = this; + var spansBuilder = result.Spans.ToBuilder(); + for (var i = 0; i < result.Spans.Length; i++) + { + var newStartLinePosition = new LinePosition(result.Spans[i].Span.StartLinePosition.Line + offset, result.Spans[i].Span.StartLinePosition.Character); + var newEndLinePosition = new LinePosition(result.Spans[i].Span.EndLinePosition.Line + offset, result.Spans[i].Span.EndLinePosition.Character); + + spansBuilder[i] = new DiagnosticLocation(new FileLinePositionSpan(result.Spans[i].Span.Path, newStartLinePosition, newEndLinePosition), result.Spans[i].Options); + } + + return new DiagnosticResult( + spans: spansBuilder.MoveToImmutable(), + suppressMessage: _suppressMessage, + message: _message, + severity: Severity, + options: Options, + id: Id, + messageFormat: MessageFormat, + messageArguments: MessageArguments); + } + + private DiagnosticResult AppendSpan(FileLinePositionSpan span, DiagnosticLocationOptions options) + { + return new DiagnosticResult( + spans: Spans.Add(new DiagnosticLocation(span, options)), + suppressMessage: _suppressMessage, + message: _message, + severity: Severity, + options: Options, + id: Id, + messageFormat: MessageFormat, + messageArguments: MessageArguments); + } + + public override string ToString() + { + var builder = new StringBuilder(); + if (HasLocation) + { + var location = Spans[0]; + builder.Append(location.Span.Path == string.Empty ? "?" : location.Span.Path); + builder.Append("("); + builder.Append(location.Span.StartLinePosition.Line + 1); + builder.Append(","); + builder.Append(location.Span.StartLinePosition.Character + 1); + if (!location.Options.HasFlag(DiagnosticLocationOptions.IgnoreLength)) + { + builder.Append(","); + builder.Append(location.Span.EndLinePosition.Line + 1); + builder.Append(","); + builder.Append(location.Span.EndLinePosition.Character + 1); + } + + builder.Append("): "); + } + + builder.Append(Severity.ToString().ToLowerInvariant()); + builder.Append(" "); + builder.Append(Id); + + var message = Message; + if (message != null) + { + builder.Append(": ").Append(message); + } + + return builder.ToString(); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/EmptyDiagnosticAnalyzer.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/EmptyDiagnosticAnalyzer.cs new file mode 100644 index 000000000..3ce4b7f52 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/EmptyDiagnosticAnalyzer.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Defines a which does not report any diagnostics. + /// + [SuppressMessage("MicrosoftCodeAnalysisCorrectness", "RS1001:Missing diagnostic analyzer attribute.", Justification = "This helper type for unit testing is not language specific, and is never actually provided as an analyzer for projects to consume.")] + public sealed class EmptyDiagnosticAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Empty; + + public override void Initialize(AnalysisContext context) + { + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/EnsureNoWorkspacesDesktopReferenceA.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/EnsureNoWorkspacesDesktopReferenceA.cs new file mode 100644 index 000000000..cff5af20a --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/EnsureNoWorkspacesDesktopReferenceA.cs @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#if DEBUG + +namespace Microsoft.CodeAnalysis +{ + /// + /// This type is part of forcing a build error in the Code Fix assembly if a reference to + /// Microsoft.CodeAnalysis.Workspaces.Desktop is included. + /// + internal class FileTextLoader { } +} + +#endif diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ExceptionUtilities.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ExceptionUtilities.cs new file mode 100644 index 000000000..906a76940 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ExceptionUtilities.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.CodeAnalysis.Testing +{ + internal static class ExceptionUtilities + { + public static Exception Unreachable => new InvalidOperationException("This program location is thought to be unreachable."); + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/CodeActionExtensions.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/CodeActionExtensions.cs new file mode 100644 index 000000000..5ad2d91a6 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/CodeActionExtensions.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; +using Microsoft.CodeAnalysis.CodeActions; + +namespace Microsoft.CodeAnalysis.Testing +{ + internal static class CodeActionExtensions + { + public static ImmutableArray GetNestedActions(this CodeAction action) + { + var property = typeof(CodeAction).GetTypeInfo().DeclaredProperties.SingleOrDefault(property => property.Name == "NestedCodeActions"); + if (property is null) + { + return ImmutableArray.Empty; + } + + return (ImmutableArray)(property.GetValue(action) ?? throw new NotSupportedException()); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/ComposableCatalogExtensions.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/ComposableCatalogExtensions.cs new file mode 100644 index 000000000..1d18cc532 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/ComposableCatalogExtensions.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.Composition; +using Microsoft.VisualStudio.Composition.Reflection; + +namespace Microsoft.CodeAnalysis.Testing +{ + internal static class ComposableCatalogExtensions + { + public static ComposableCatalog WithDocumentTextDifferencingService(this ComposableCatalog catalog) + { + var assemblyQualifiedServiceTypeName = "Microsoft.CodeAnalysis.IDocumentTextDifferencingService, " + typeof(Workspace).GetTypeInfo().Assembly.GetName().ToString(); + + // Check to see if IDocumentTextDifferencingService is exported + foreach (var part in catalog.Parts) + { + foreach (var pair in part.ExportDefinitions) + { + var exportDefinition = pair.Value; + if (exportDefinition.ContractName != "Microsoft.CodeAnalysis.Host.IWorkspaceService") + { + continue; + } + + if (!exportDefinition.Metadata.TryGetValue("ServiceType", out var value) + || !(value is string serviceType)) + { + continue; + } + + if (serviceType != assemblyQualifiedServiceTypeName) + { + continue; + } + + // The service is exported by default + return catalog; + } + } + + // If IDocumentTextDifferencingService is not exported by default, export it manually + var manualExportDefinition = new ExportDefinition( + typeof(IWorkspaceService).FullName, + metadata: new Dictionary + { + { "ExportTypeIdentity", typeof(IWorkspaceService).FullName }, + { nameof(ExportWorkspaceServiceAttribute.ServiceType), assemblyQualifiedServiceTypeName }, + { nameof(ExportWorkspaceServiceAttribute.Layer), ServiceLayer.Default }, + { typeof(CreationPolicy).FullName!, CreationPolicy.Shared }, + { "ContractType", typeof(IWorkspaceService) }, + { "ContractName", null }, + }); + + var serviceImplType = typeof(Workspace).GetTypeInfo().Assembly.GetType("Microsoft.CodeAnalysis.DefaultDocumentTextDifferencingService"); + RoslynDebug.AssertNotNull(serviceImplType); + + return catalog.AddPart(new ComposablePartDefinition( + TypeRef.Get(serviceImplType, Resolver.DefaultInstance), + new Dictionary { { "SharingBoundary", null } }, + new[] { manualExportDefinition }, + new Dictionary>(), + Enumerable.Empty(), + sharingBoundary: string.Empty, + default(MethodRef), + MethodRef.Get(serviceImplType.GetConstructors(BindingFlags.Instance | BindingFlags.Public).First(), Resolver.DefaultInstance), + new List(), + CreationPolicy.Shared, + new[] { typeof(Workspace).GetTypeInfo().Assembly.GetName() }, + isSharingBoundaryInferred: false)); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/DictionaryExtensions.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/DictionaryExtensions.cs new file mode 100644 index 000000000..a2b88f208 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/DictionaryExtensions.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.CodeAnalysis.Testing +{ + internal static class DictionaryExtensions + { + public static void AddRange(this IDictionary dictionary, IEnumerable> items) + where TKey : notnull + { + foreach (var (key, value) in items) + { + dictionary.Add(key, value); + } + } + + // Copied from ConcurrentDictionary since IDictionary doesn't have this useful method + public static TValue GetOrAdd(this IDictionary dictionary, TKey key, Func function) + where TKey : notnull + { + if (!dictionary.TryGetValue(key, out var value)) + { + value = function(key); + dictionary.Add(key, value); + } + + return value; + } + + public static TValue GetOrAdd(this IDictionary dictionary, TKey key, Func function) + where TKey : notnull + => dictionary.GetOrAdd(key, _ => function()); + + public static void Deconstruct(this KeyValuePair pair, out TKey key, out TValue value) + { + key = pair.Key; + value = pair.Value; + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/ExportProviderExtensions.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/ExportProviderExtensions.cs new file mode 100644 index 000000000..d4f1b3850 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/ExportProviderExtensions.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Composition; +using System.Composition.Hosting.Core; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using Microsoft.VisualStudio.Composition; + +namespace Microsoft.CodeAnalysis.Testing +{ + 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, [NotNullWhen(true)] out object? export) + { + var importMany = contract.MetadataConstraints.Contains(new KeyValuePair("IsImportMany", true)); + var (contractType, metadataType) = 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 }); + RoslynDebug.AssertNotNull(export); + } + else + { + 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 }); + RoslynDebug.AssertNotNull(export); + } + + return true; + } + + private (Type exportType, Type? metadataType) GetContractType(Type contractType, bool importMany) + { + 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); + } + else if (contractType.GetGenericTypeDefinition() == typeof(Lazy<,>)) + { + return (contractType.GenericTypeArguments[0], contractType.GenericTypeArguments[1]); + } + else + { + throw new NotSupportedException(); + } + } + + throw new NotSupportedException(); + } + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/IEnumerableExtensions.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/IEnumerableExtensions.cs new file mode 100644 index 000000000..9e94d7360 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/IEnumerableExtensions.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.CodeAnalysis.Testing +{ + internal static class IEnumerableExtensions + { + private static readonly Func s_notNullTest = x => x is object; + + public static DiagnosticResult[] ToOrderedArray(this IEnumerable diagnosticResults) + { + return diagnosticResults + .OrderBy(diagnosticResult => diagnosticResult.Spans.FirstOrDefault().Span.Path, StringComparer.Ordinal) + .ThenBy(diagnosticResult => diagnosticResult.Spans.FirstOrDefault().Span.Span.Start.Line) + .ThenBy(diagnosticResult => diagnosticResult.Spans.FirstOrDefault().Span.Span.Start.Character) + .ThenBy(diagnosticResult => diagnosticResult.Spans.FirstOrDefault().Span.Span.End.Line) + .ThenBy(diagnosticResult => diagnosticResult.Spans.FirstOrDefault().Span.Span.End.Character) + .ThenBy(diagnosticResult => diagnosticResult.Id, StringComparer.Ordinal) + .ToArray(); + } + + internal static IEnumerable WhereNotNull(this IEnumerable source) + where T : class + { + return source.Where(s_notNullTest)!; + } + + public static T? SingleOrNull(this IEnumerable source) + where T : struct + { + return source.Select(value => (T?)value).SingleOrDefault(); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/IVerifierExtensions.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/IVerifierExtensions.cs new file mode 100644 index 000000000..7e0f3d551 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/IVerifierExtensions.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Text; +using DiffPlex; +using DiffPlex.Chunkers; +using DiffPlex.DiffBuilder; +using DiffPlex.DiffBuilder.Model; + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Extensions on the interface. + /// + public static class IVerifierExtensions + { + private static readonly IChunker s_lineChunker = new LineChunker(); + private static readonly IChunker s_lineEndingsPreservingChunker = new LineEndingsPreservingChunker(); + private static readonly InlineDiffBuilder s_diffBuilder = new InlineDiffBuilder(new Differ()); + + /// + /// Asserts that two strings are equal, and prints a diff between the two if they are not. + /// + /// The verifier instance. + /// The expected string. This is presented as the "baseline/before" side in the diff. + /// The actual string. This is presented as the changed or "after" side in the diff. + /// The message to precede the diff, if the values are not equal. + public static void EqualOrDiff(this IVerifier verifier, string expected, string actual, string? message = null) + { + Requires.NotNull(verifier, nameof(verifier)); + + if (expected != actual) + { + var diff = s_diffBuilder.BuildDiffModel(expected, actual, ignoreWhitespace: false, ignoreCase: false, s_lineChunker); + var messageBuilder = new StringBuilder(); + messageBuilder.AppendLine( + string.IsNullOrEmpty(message) + ? "Actual and expected values differ. Expected shown in baseline of diff:" + : message); + + if (!diff.Lines.Any(line => line.Type == ChangeType.Inserted || line.Type == ChangeType.Deleted)) + { + // We have a failure only caused by line ending differences; recalculate with line endings visible + diff = s_diffBuilder.BuildDiffModel(expected, actual, ignoreWhitespace: false, ignoreCase: false, s_lineEndingsPreservingChunker); + } + + foreach (var line in diff.Lines) + { + switch (line.Type) + { + case ChangeType.Inserted: + messageBuilder.Append("+"); + break; + case ChangeType.Deleted: + messageBuilder.Append("-"); + break; + default: + messageBuilder.Append(" "); + break; + } + + messageBuilder.AppendLine(line.Text.Replace("\r", "").Replace("\n", "")); + } + + verifier.Fail(messageBuilder.ToString()); + } + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/ProjectExtensions.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/ProjectExtensions.cs new file mode 100644 index 000000000..c11cdb8a1 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/ProjectExtensions.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Microsoft.CodeAnalysis.Testing +{ + internal static class ProjectExtensions + { + private static readonly Func> s_analyzerConfigDocuments; + + static ProjectExtensions() + { + var analyzerConfigDocumentType = typeof(Project).GetTypeInfo().Assembly.GetType("Microsoft.CodeAnalysis.AnalyzerConfigDocument"); + if (analyzerConfigDocumentType is { }) + { + var analyzerConfigDocumentsProperty = typeof(Project).GetProperty(nameof(AnalyzerConfigDocuments), typeof(IEnumerable<>).MakeGenericType(analyzerConfigDocumentType)); + if (analyzerConfigDocumentsProperty is { GetMethod: { } getMethod }) + { + s_analyzerConfigDocuments = (Func>)getMethod.CreateDelegate(typeof(Func>), target: null); + } + else + { + s_analyzerConfigDocuments = project => Enumerable.Empty(); + } + } + else + { + s_analyzerConfigDocuments = project => Enumerable.Empty(); + } + } + + public static IEnumerable AnalyzerConfigDocuments(this Project project) + => s_analyzerConfigDocuments(project); + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/SolutionExtensions.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/SolutionExtensions.cs new file mode 100644 index 000000000..2b4e18236 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Extensions/SolutionExtensions.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Testing +{ + internal static class SolutionExtensions + { + private static readonly Func?, string?, Solution> s_addAnalyzerConfigDocument; + + static SolutionExtensions() + { + var methodInfo = typeof(Solution).GetMethod(nameof(AddAnalyzerConfigDocument), new[] { typeof(DocumentId), typeof(string), typeof(SourceText), typeof(IEnumerable), typeof(string) }); + if (methodInfo is { }) + { + s_addAnalyzerConfigDocument = (Func?, string?, Solution>)methodInfo.CreateDelegate(typeof(Func, string, Solution>), target: null); + } + else + { + s_addAnalyzerConfigDocument = (solution, documentId, name, text, folders, filePath) => throw new NotSupportedException(); + } + } + + public static Solution AddAnalyzerConfigDocument(this Solution solution, DocumentId documentId, string name, SourceText text, IEnumerable? folders = null, string? filePath = null) + { + return s_addAnalyzerConfigDocument(solution, documentId, name, text, folders, filePath); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/IVerifier.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/IVerifier.cs new file mode 100644 index 000000000..ad6da23fd --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/IVerifier.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.CodeAnalysis.Testing +{ + public interface IVerifier + { + /// + /// Verify that a specified is empty. + /// + /// The type of elements in the collection. + /// The name of the collection. + /// The collection. + void Empty(string collectionName, IEnumerable collection); + + /// + /// Verify that two items are equal. + /// + /// The type of item to compare. + /// The expected item. + /// The actual item. + /// The message to report if the items are not equal, or to use a default message. + void Equal(T expected, T actual, string? message = null); + + /// + /// Verify that a value is . + /// + /// The value. + /// The message to report if the value is not , or to use a default message. + void True([DoesNotReturnIf(false)] bool assert, string? message = null); + + /// + /// Verify that a value is . + /// + /// The value. + /// The message to report if the value is not , or to use a default message. + void False([DoesNotReturnIf(true)] bool assert, string? message = null); + + /// + /// Called to indicate validation has failed. + /// + /// The failure message to report, or to use a default message. + [DoesNotReturn] + void Fail(string? message = null); + + /// + /// Verifies that a specific language is supported by this verifier. + /// + /// The language. + /// + void LanguageIsSupported(string language); + + /// + /// Verify that a specified is not empty. + /// + /// The type of elements in the collection. + /// The name of the collection. + /// The collection. + void NotEmpty(string collectionName, IEnumerable collection); + + /// + /// Verify that two collections are equal. + /// + /// The type of elements in the collection. + /// The expected collection. + /// The actual collection. + /// The comparer to use for elements in the collection, or to use the default comparer. + /// The message to report if the collections are not equal, or to use a default message. + void SequenceEqual(IEnumerable expected, IEnumerable actual, IEqualityComparer? equalityComparer = null, string? message = null); + + /// + /// Creates a new verifier for validation within a specific context. + /// + /// The context. + /// A new which includes the specified in failure messages. + IVerifier PushContext(string context); + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/MarkupMode.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/MarkupMode.cs new file mode 100644 index 000000000..72529598e --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/MarkupMode.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Indicates the manner in which markup syntax is treated within test inputs and outputs. + /// + /// + public enum MarkupMode + { + /// + /// Markup syntax is disabled, and any syntax which could be treated as markup is preserved in the contents of + /// sources and additional files. + /// + None, + + /// + /// Markup syntax is allowed, but diagnostics suggested by markup syntax is ignored. + /// + Ignore, + + /// + /// Markup syntax is allowed, but fixable diagnostics suggested by markup syntax are ignored. + /// + IgnoreFixable, + + /// + /// Markup syntax is allowed, and all diagnostics indicated by markup syntax are preserved. + /// + Allow, + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/MarkupOptions.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/MarkupOptions.cs new file mode 100644 index 000000000..502518223 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/MarkupOptions.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Specifies additional options for the markup parser. + /// + [Flags] + public enum MarkupOptions + { + /// + /// No additional markup options are specified. + /// + None = 0, + + /// + /// Use the first matching diagnostic descriptor when multiple diagnostics match the syntax. By default, this + /// option is not specified and the markup parser will fail when the syntax does not represent a unique + /// descriptor. + /// + UseFirstDescriptor = 0x0001, + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/MatchQuality.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/MatchQuality.cs new file mode 100644 index 000000000..bb0313b7b --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/MatchQuality.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.CodeAnalysis.Testing +{ + internal readonly struct MatchQuality : IComparable, IEquatable + { + public static readonly MatchQuality Full = new MatchQuality(0); + public static readonly MatchQuality None = new MatchQuality(4); + + private readonly int _value; + + public MatchQuality(int value) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _value = value; + } + + public static MatchQuality operator +(MatchQuality left, MatchQuality right) + => new MatchQuality(left._value + right._value); + + public static MatchQuality operator -(MatchQuality left, MatchQuality right) + => new MatchQuality(left._value - right._value); + + public static bool operator ==(MatchQuality left, MatchQuality right) + => left.Equals(right); + + public static bool operator !=(MatchQuality left, MatchQuality right) + => !left.Equals(right); + + public static bool operator <(MatchQuality left, MatchQuality right) + => left._value < right._value; + + public static bool operator <=(MatchQuality left, MatchQuality right) + => left._value <= right._value; + + public static bool operator >(MatchQuality left, MatchQuality right) + => left._value > right._value; + + public static bool operator >=(MatchQuality left, MatchQuality right) + => left._value >= right._value; + + public static MatchQuality RemainingUnmatched(int count) + => new MatchQuality(None._value * count); + + public int CompareTo(MatchQuality other) + => _value.CompareTo(other._value); + + public override bool Equals(object? obj) + => obj is MatchQuality quality && Equals(quality); + + public bool Equals(MatchQuality other) + => _value == other._value; + + public override int GetHashCode() + => _value; + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/MetadataReferenceCollection.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/MetadataReferenceCollection.cs new file mode 100644 index 000000000..3491704ac --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/MetadataReferenceCollection.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Reflection; + +namespace Microsoft.CodeAnalysis.Testing +{ + public class MetadataReferenceCollection : List + { + private static readonly ConcurrentDictionary s_referencesFromFiles = + new ConcurrentDictionary(); + + public void Add(Assembly assembly) + { + Add(GetOrCreateReference(assembly.Location)); + } + + public void Add(string path) + { + Add(GetOrCreateReference(path)); + } + + private static MetadataReference GetOrCreateReference(string path) + { + return s_referencesFromFiles.GetOrAdd(path, p => MetadataReferences.CreateReferenceFromFile(p)); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/MetadataReferences.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/MetadataReferences.cs new file mode 100644 index 000000000..4d8dadce3 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/MetadataReferences.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.CodeAnalysis; + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Metadata references used to create test projects. + /// + public static class MetadataReferences + { + private static readonly Func s_createDocumentationProvider; + + static MetadataReferences() + { + Func createDocumentationProvider = _ => null; + + var xmlDocumentationProvider = typeof(Workspace).GetTypeInfo().Assembly.GetType("Microsoft.CodeAnalysis.XmlDocumentationProvider"); + if (xmlDocumentationProvider is object) + { + var createFromFile = xmlDocumentationProvider.GetTypeInfo().GetMethod("CreateFromFile", new[] { typeof(string) }); + if (createFromFile is object) + { + var xmlDocCommentFilePath = Expression.Parameter(typeof(string), "xmlDocCommentFilePath"); + var body = Expression.Convert( + Expression.Call(createFromFile, xmlDocCommentFilePath), + typeof(DocumentationProvider)); + var expression = Expression.Lambda>(body, xmlDocCommentFilePath); + createDocumentationProvider = expression.Compile(); + } + } + + s_createDocumentationProvider = createDocumentationProvider; + } + + internal static MetadataReference CreateReferenceFromFile(string path) + { + var documentationFile = Path.ChangeExtension(path, ".xml"); + return MetadataReference.CreateFromFile(path, documentation: s_createDocumentationProvider(documentationFile)); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Microsoft.CodeAnalysis.Analyzer.Testing.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Microsoft.CodeAnalysis.Analyzer.Testing.csproj new file mode 100644 index 000000000..91f8237e9 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Microsoft.CodeAnalysis.Analyzer.Testing.csproj @@ -0,0 +1,65 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.Analyzer.Testing + Microsoft.CodeAnalysis.Testing + + + + true + Roslyn Analyzer Test Framework Common Types. + Roslyn Analyzer Test Framework Common Types + Roslyn Analyzer Test Framework Common + + + + + + 4.5.3 + + + + + 4.9.4 + + + + + 4.6.4 + + + + + 5.6.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Model/EvaluatedProjectState.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Model/EvaluatedProjectState.cs new file mode 100644 index 000000000..421c09106 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/Model/EvaluatedProjectState.cs @@ -0,0 +1,145 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Testing.Model +{ + /// + /// Represents an evaluated . + /// + public sealed class EvaluatedProjectState + { + public EvaluatedProjectState(ProjectState state, ReferenceAssemblies defaultReferenceAssemblies) + : this( + state.Name, + state.AssemblyName, + state.Language, + state.ReferenceAssemblies ?? defaultReferenceAssemblies, + state.OutputKind ?? OutputKind.DynamicallyLinkedLibrary, + state.DocumentationMode ?? DocumentationMode.Diagnose, + state.Sources.ToImmutableArray(), + state.GeneratedSources.ToImmutableArray(), + state.AdditionalFiles.ToImmutableArray(), + state.AnalyzerConfigFiles.ToImmutableArray(), + state.AdditionalProjectReferences.ToImmutableArray(), + state.AdditionalReferences.ToImmutableArray(), + ImmutableArray.Empty) + { + } + + private EvaluatedProjectState( + string name, + string assemblyName, + string language, + ReferenceAssemblies referenceAssemblies, + OutputKind outputKind, + DocumentationMode documentationMode, + ImmutableArray<(string filename, SourceText content)> sources, + ImmutableArray<(string filename, SourceText content)> generatedSources, + ImmutableArray<(string filename, SourceText content)> additionalFiles, + ImmutableArray<(string filename, SourceText content)> analyzerConfigFiles, + ImmutableArray additionalProjectReferences, + ImmutableArray additionalReferences, + ImmutableArray additionalDiagnostics) + { + Name = name; + AssemblyName = assemblyName; + Language = language; + ReferenceAssemblies = referenceAssemblies; + OutputKind = outputKind; + DocumentationMode = documentationMode; + Sources = sources; + GeneratedSources = generatedSources; + AdditionalFiles = additionalFiles; + AnalyzerConfigFiles = analyzerConfigFiles; + AdditionalProjectReferences = additionalProjectReferences; + AdditionalReferences = additionalReferences; + AdditionalDiagnostics = additionalDiagnostics; + } + + public string Name { get; } + + public string AssemblyName { get; } + + public string Language { get; } + + public ReferenceAssemblies ReferenceAssemblies { get; } + + public OutputKind OutputKind { get; } + + public DocumentationMode DocumentationMode { get; } + + public ImmutableArray<(string filename, SourceText content)> Sources { get; } + + public ImmutableArray<(string filename, SourceText content)> GeneratedSources { get; } + + public ImmutableArray<(string filename, SourceText content)> AdditionalFiles { get; } + + public ImmutableArray<(string filename, SourceText content)> AnalyzerConfigFiles { get; } + + public ImmutableArray AdditionalProjectReferences { get; } + + public ImmutableArray AdditionalReferences { get; } + + public ImmutableArray AdditionalDiagnostics { get; } + + public EvaluatedProjectState WithSources(ImmutableArray<(string filename, SourceText content)> sources) + { + if (sources == Sources) + { + return this; + } + + return With(sources: sources); + } + + public EvaluatedProjectState WithAdditionalDiagnostics(ImmutableArray additionalDiagnostics) + { + if (additionalDiagnostics == AdditionalDiagnostics) + { + return this; + } + + return With(additionalDiagnostics: additionalDiagnostics); + } + + private EvaluatedProjectState With( + Optional name = default, + Optional assemblyName = default, + Optional language = default, + Optional referenceAssemblies = default, + Optional outputKind = default, + Optional documentationMode = default, + Optional> sources = default, + Optional> generatedSources = default, + Optional> additionalFiles = default, + Optional> analyzerConfigFiles = default, + Optional> additionalProjectReferences = default, + Optional> additionalReferences = default, + Optional> additionalDiagnostics = default) + { + return new EvaluatedProjectState( + GetValueOrDefault(name, Name), + GetValueOrDefault(assemblyName, AssemblyName), + GetValueOrDefault(language, Language), + GetValueOrDefault(referenceAssemblies, ReferenceAssemblies), + GetValueOrDefault(outputKind, OutputKind), + GetValueOrDefault(documentationMode, DocumentationMode), + GetValueOrDefault(sources, Sources), + GetValueOrDefault(generatedSources, GeneratedSources), + GetValueOrDefault(additionalFiles, AdditionalFiles), + GetValueOrDefault(analyzerConfigFiles, AnalyzerConfigFiles), + GetValueOrDefault(additionalProjectReferences, AdditionalProjectReferences), + GetValueOrDefault(additionalReferences, AdditionalReferences), + GetValueOrDefault(additionalDiagnostics, AdditionalDiagnostics)); + } + + private static T GetValueOrDefault(Optional optionalValue, T defaultValue) + { + return optionalValue.HasValue ? optionalValue.Value : defaultValue; + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/NullableAttributes.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/NullableAttributes.cs new file mode 100644 index 000000000..3aeb6d51f --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/NullableAttributes.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +// + +// This was copied from https://github.com/dotnet/coreclr/blob/60f1e6265bd1039f023a82e0643b524d6aaf7845/src/System.Private.CoreLib/shared/System/Diagnostics/CodeAnalysis/NullableAttributes.cs +// and updated to have the scope of the attributes be internal. + +#if !NETCOREAPP +#nullable enable + +namespace System.Diagnostics.CodeAnalysis +{ + /// Specifies that null is allowed as an input even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] + internal sealed class AllowNullAttribute : Attribute { } + + /// Specifies that null is disallowed as an input even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] + internal sealed class DisallowNullAttribute : Attribute { } + + /// Specifies that an output may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + internal sealed class MaybeNullAttribute : Attribute { } + + /// Specifies that an output will not be null even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + internal sealed class NotNullAttribute : Attribute { } + + /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class MaybeNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter may be null. + /// + public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class NotNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that the output will be non-null if the named parameter is non-null. + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] + internal sealed class NotNullIfNotNullAttribute : Attribute + { + /// Initializes the attribute with the associated parameter name. + /// + /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. + /// + public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; + + /// Gets the associated parameter name. + public string ParameterName { get; } + } + + /// Applied to a method that will never return under any circumstance. + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + internal sealed class DoesNotReturnAttribute : Attribute { } + + /// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class DoesNotReturnIfAttribute : Attribute + { + /// Initializes the attribute with the specified parameter value. + /// + /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to + /// the associated parameter matches this value. + /// + public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; + + /// Gets the condition parameter value. + public bool ParameterValue { get; } + } +} + +#endif diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PackageIdentity.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PackageIdentity.cs new file mode 100644 index 000000000..5a82223b0 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PackageIdentity.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using NuGet.Versioning; + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Represents the core identity of a NuGet package. + /// + /// + public sealed class PackageIdentity + { + /// + /// Initializes a new instance of the class with the specified name and version. + /// + /// The package name. + /// The package version. + /// + /// If is . + /// -or- + /// If is . + /// + public PackageIdentity(string id, string version) + { + Id = id ?? throw new ArgumentNullException(nameof(id)); + Version = version ?? throw new ArgumentNullException(nameof(version)); + } + + /// + /// Gets the package name. + /// + /// + public string Id { get; } + + /// + /// Gets the package version. + /// + /// + public string Version { get; } + + internal NuGet.Packaging.Core.PackageIdentity ToNuGetIdentity() + { + return new NuGet.Packaging.Core.PackageIdentity(Id, NuGetVersion.Parse(Version)); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ProjectCollection.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ProjectCollection.cs new file mode 100644 index 000000000..12b69c14e --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ProjectCollection.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.CodeAnalysis.Testing +{ + public class ProjectCollection : Dictionary + { + private readonly string _defaultLanguage; + private readonly string _defaultExtension; + + public ProjectCollection(string defaultLanguage, string defaultExtension) + { + _defaultLanguage = defaultLanguage; + _defaultExtension = defaultExtension; + } + + public new ProjectState this[string projectName] + { + get + { + if (TryGetValue(projectName, out var project)) + { + return project; + } + + return this[projectName, _defaultLanguage]; + } + } + + public ProjectState this[string projectName, string language] + { + get + { + string extension; + if (language == _defaultLanguage) + { + extension = _defaultExtension; + } + else + { + extension = language switch + { + LanguageNames.CSharp => "cs", + LanguageNames.VisualBasic => "vb", + _ => throw new ArgumentOutOfRangeException(nameof(language)), + }; + } + + var project = this.GetOrAdd(projectName, () => new ProjectState(projectName, language, $"/{projectName}/Test", extension)); + if (project.Language != language) + { + throw new InvalidOperationException(); + } + + return project; + } + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ProjectState.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ProjectState.cs new file mode 100644 index 000000000..8bde053df --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ProjectState.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Testing +{ + public class ProjectState + { + public ProjectState(string name, string language, string defaultPrefix, string defaultExtension) + { + Name = name; + Language = language; + DefaultPrefix = defaultPrefix; + DefaultExtension = defaultExtension; + + Sources = new SourceFileList(defaultPrefix, defaultExtension); + } + + internal ProjectState(ProjectState sourceState) + { + Name = sourceState.Name; + Language = sourceState.Language; + ReferenceAssemblies = sourceState.ReferenceAssemblies; + OutputKind = sourceState.OutputKind; + DocumentationMode = sourceState.DocumentationMode; + DefaultPrefix = sourceState.DefaultPrefix; + DefaultExtension = sourceState.DefaultExtension; + Sources = new SourceFileList(DefaultPrefix, DefaultExtension); + + Sources.AddRange(sourceState.Sources); + GeneratedSources.AddRange(sourceState.GeneratedSources); + AdditionalFiles.AddRange(sourceState.AdditionalFiles); + AnalyzerConfigFiles.AddRange(sourceState.AnalyzerConfigFiles); + AdditionalFilesFactories.AddRange(sourceState.AdditionalFilesFactories); + AdditionalProjectReferences.AddRange(sourceState.AdditionalProjectReferences); + } + + public string Name { get; } + + public string AssemblyName => Name; + + public string Language { get; } + + /// + /// Gets or sets the reference assemblies to use for the project. + /// + /// + /// A instance to use specific reference assemblies; otherwise, + /// to inherit the reference assemblies from + /// . + /// + public ReferenceAssemblies? ReferenceAssemblies { get; set; } + + public OutputKind? OutputKind { get; set; } + + public DocumentationMode? DocumentationMode { get; set; } + + /// + /// Gets the set of source files for analyzer or code fix testing. Files may be added to this list using one of + /// the methods. + /// + public SourceFileList Sources { get; } + + public SourceFileCollection GeneratedSources { get; } = new SourceFileCollection(); + + public SourceFileCollection AdditionalFiles { get; } = new SourceFileCollection(); + + public SourceFileCollection AnalyzerConfigFiles { get; } = new SourceFileCollection(); + + public List>> AdditionalFilesFactories { get; } = new List>>(); + + public List AdditionalProjectReferences { get; } = new List(); + + public MetadataReferenceCollection AdditionalReferences { get; } = new MetadataReferenceCollection(); + + private protected string DefaultPrefix { get; } + + private protected string DefaultExtension { get; } + } +} diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/AssemblyInfo.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PublicAPI.Shipped.txt similarity index 100% rename from src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/AssemblyInfo.vb rename to src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PublicAPI.Shipped.txt diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..7c946dcb0 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PublicAPI.Unshipped.txt @@ -0,0 +1,347 @@ +Microsoft.CodeAnalysis.Testing.AnalyzerTest +Microsoft.CodeAnalysis.Testing.AnalyzerTest.AnalyzerTest() -> void +Microsoft.CodeAnalysis.Testing.AnalyzerTest.CompilerDiagnostics.get -> Microsoft.CodeAnalysis.Testing.CompilerDiagnostics +Microsoft.CodeAnalysis.Testing.AnalyzerTest.CompilerDiagnostics.set -> void +Microsoft.CodeAnalysis.Testing.AnalyzerTest.CreateProjectAsync(Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState primaryProject, System.Collections.Immutable.ImmutableArray additionalProjects, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +Microsoft.CodeAnalysis.Testing.AnalyzerTest.CreateWorkspace() -> Microsoft.CodeAnalysis.Workspace +Microsoft.CodeAnalysis.Testing.AnalyzerTest.DiagnosticVerifier.get -> System.Action +Microsoft.CodeAnalysis.Testing.AnalyzerTest.DiagnosticVerifier.set -> void +Microsoft.CodeAnalysis.Testing.AnalyzerTest.DisabledDiagnostics.get -> System.Collections.Generic.List +Microsoft.CodeAnalysis.Testing.AnalyzerTest.ExpectedDiagnostics.get -> System.Collections.Generic.List +Microsoft.CodeAnalysis.Testing.AnalyzerTest.FormatVerifierMessage(System.Collections.Immutable.ImmutableArray analyzers, Microsoft.CodeAnalysis.Diagnostic actual, Microsoft.CodeAnalysis.Testing.DiagnosticResult expected, string message) -> string +Microsoft.CodeAnalysis.Testing.AnalyzerTest.GetSortedDiagnosticsAsync(Microsoft.CodeAnalysis.Solution solution, System.Collections.Immutable.ImmutableArray analyzers, System.Collections.Immutable.ImmutableArray<(Microsoft.CodeAnalysis.Project project, Microsoft.CodeAnalysis.Diagnostic diagnostic)> additionalDiagnostics, Microsoft.CodeAnalysis.Testing.CompilerDiagnostics compilerDiagnostics, Microsoft.CodeAnalysis.Testing.IVerifier verifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +Microsoft.CodeAnalysis.Testing.AnalyzerTest.MarkupOptions.get -> Microsoft.CodeAnalysis.Testing.MarkupOptions +Microsoft.CodeAnalysis.Testing.AnalyzerTest.MarkupOptions.set -> void +Microsoft.CodeAnalysis.Testing.AnalyzerTest.MatchDiagnosticsTimeout.get -> System.TimeSpan +Microsoft.CodeAnalysis.Testing.AnalyzerTest.MatchDiagnosticsTimeout.set -> void +Microsoft.CodeAnalysis.Testing.AnalyzerTest.OptionsTransforms.get -> System.Collections.Generic.List> +Microsoft.CodeAnalysis.Testing.AnalyzerTest.ReferenceAssemblies.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +Microsoft.CodeAnalysis.Testing.AnalyzerTest.ReferenceAssemblies.set -> void +Microsoft.CodeAnalysis.Testing.AnalyzerTest.RunAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +Microsoft.CodeAnalysis.Testing.AnalyzerTest.SolutionTransforms.get -> System.Collections.Generic.List> +Microsoft.CodeAnalysis.Testing.AnalyzerTest.TestBehaviors.get -> Microsoft.CodeAnalysis.Testing.TestBehaviors +Microsoft.CodeAnalysis.Testing.AnalyzerTest.TestBehaviors.set -> void +Microsoft.CodeAnalysis.Testing.AnalyzerTest.TestCode.set -> void +Microsoft.CodeAnalysis.Testing.AnalyzerTest.TestState.get -> Microsoft.CodeAnalysis.Testing.SolutionState +Microsoft.CodeAnalysis.Testing.AnalyzerTest.VerifyDiagnosticsAsync(Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState primaryProject, System.Collections.Immutable.ImmutableArray additionalProjects, Microsoft.CodeAnalysis.Testing.DiagnosticResult[] expected, Microsoft.CodeAnalysis.Testing.IVerifier verifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +Microsoft.CodeAnalysis.Testing.AnalyzerTest.XmlReferences.get -> System.Collections.Generic.Dictionary +Microsoft.CodeAnalysis.Testing.AnalyzerVerifier +Microsoft.CodeAnalysis.Testing.AnalyzerVerifier.AnalyzerVerifier() -> void +Microsoft.CodeAnalysis.Testing.CodeActionTest +Microsoft.CodeAnalysis.Testing.CodeActionTest.ApplyCodeActionAsync(Microsoft.CodeAnalysis.Project project, Microsoft.CodeAnalysis.CodeActions.CodeAction codeAction, Microsoft.CodeAnalysis.Testing.IVerifier verifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +Microsoft.CodeAnalysis.Testing.CodeActionTest.CodeActionEquivalenceKey.get -> string +Microsoft.CodeAnalysis.Testing.CodeActionTest.CodeActionEquivalenceKey.set -> void +Microsoft.CodeAnalysis.Testing.CodeActionTest.CodeActionIndex.get -> int? +Microsoft.CodeAnalysis.Testing.CodeActionTest.CodeActionIndex.set -> void +Microsoft.CodeAnalysis.Testing.CodeActionTest.CodeActionTest() -> void +Microsoft.CodeAnalysis.Testing.CodeActionTest.CodeActionValidationMode.get -> Microsoft.CodeAnalysis.Testing.CodeActionValidationMode +Microsoft.CodeAnalysis.Testing.CodeActionTest.CodeActionValidationMode.set -> void +Microsoft.CodeAnalysis.Testing.CodeActionTest.CodeActionVerifier.get -> System.Action +Microsoft.CodeAnalysis.Testing.CodeActionTest.CodeActionVerifier.set -> void +Microsoft.CodeAnalysis.Testing.CodeActionValidationMode +Microsoft.CodeAnalysis.Testing.CodeActionValidationMode.Full = 2 -> Microsoft.CodeAnalysis.Testing.CodeActionValidationMode +Microsoft.CodeAnalysis.Testing.CodeActionValidationMode.None = 0 -> Microsoft.CodeAnalysis.Testing.CodeActionValidationMode +Microsoft.CodeAnalysis.Testing.CodeActionValidationMode.SemanticStructure = 1 -> Microsoft.CodeAnalysis.Testing.CodeActionValidationMode +Microsoft.CodeAnalysis.Testing.CompilerDiagnostics +Microsoft.CodeAnalysis.Testing.CompilerDiagnostics.All = 4 -> Microsoft.CodeAnalysis.Testing.CompilerDiagnostics +Microsoft.CodeAnalysis.Testing.CompilerDiagnostics.Errors = 1 -> Microsoft.CodeAnalysis.Testing.CompilerDiagnostics +Microsoft.CodeAnalysis.Testing.CompilerDiagnostics.None = 0 -> Microsoft.CodeAnalysis.Testing.CompilerDiagnostics +Microsoft.CodeAnalysis.Testing.CompilerDiagnostics.Suggestions = 3 -> Microsoft.CodeAnalysis.Testing.CompilerDiagnostics +Microsoft.CodeAnalysis.Testing.CompilerDiagnostics.Warnings = 2 -> Microsoft.CodeAnalysis.Testing.CompilerDiagnostics +Microsoft.CodeAnalysis.Testing.DefaultVerifier +Microsoft.CodeAnalysis.Testing.DefaultVerifier.Context.get -> System.Collections.Immutable.ImmutableStack +Microsoft.CodeAnalysis.Testing.DefaultVerifier.DefaultVerifier() -> void +Microsoft.CodeAnalysis.Testing.DefaultVerifier.DefaultVerifier(System.Collections.Immutable.ImmutableStack context) -> void +Microsoft.CodeAnalysis.Testing.DiagnosticLocation +Microsoft.CodeAnalysis.Testing.DiagnosticLocation.DiagnosticLocation(Microsoft.CodeAnalysis.FileLinePositionSpan span, Microsoft.CodeAnalysis.Testing.DiagnosticLocationOptions options) -> void +Microsoft.CodeAnalysis.Testing.DiagnosticLocation.Options.get -> Microsoft.CodeAnalysis.Testing.DiagnosticLocationOptions +Microsoft.CodeAnalysis.Testing.DiagnosticLocation.Span.get -> Microsoft.CodeAnalysis.FileLinePositionSpan +Microsoft.CodeAnalysis.Testing.DiagnosticLocationOptions +Microsoft.CodeAnalysis.Testing.DiagnosticLocationOptions.IgnoreLength = 1 -> Microsoft.CodeAnalysis.Testing.DiagnosticLocationOptions +Microsoft.CodeAnalysis.Testing.DiagnosticLocationOptions.InterpretAsMarkupKey = 2 -> Microsoft.CodeAnalysis.Testing.DiagnosticLocationOptions +Microsoft.CodeAnalysis.Testing.DiagnosticLocationOptions.None = 0 -> Microsoft.CodeAnalysis.Testing.DiagnosticLocationOptions +Microsoft.CodeAnalysis.Testing.DiagnosticOptions +Microsoft.CodeAnalysis.Testing.DiagnosticOptions.IgnoreAdditionalLocations = 1 -> Microsoft.CodeAnalysis.Testing.DiagnosticOptions +Microsoft.CodeAnalysis.Testing.DiagnosticOptions.IgnoreSeverity = 2 -> Microsoft.CodeAnalysis.Testing.DiagnosticOptions +Microsoft.CodeAnalysis.Testing.DiagnosticOptions.None = 0 -> Microsoft.CodeAnalysis.Testing.DiagnosticOptions +Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.DiagnosticResult(Microsoft.CodeAnalysis.DiagnosticDescriptor descriptor) -> void +Microsoft.CodeAnalysis.Testing.DiagnosticResult.DiagnosticResult(string id, Microsoft.CodeAnalysis.DiagnosticSeverity severity) -> void +Microsoft.CodeAnalysis.Testing.DiagnosticResult.HasLocation.get -> bool +Microsoft.CodeAnalysis.Testing.DiagnosticResult.Id.get -> string +Microsoft.CodeAnalysis.Testing.DiagnosticResult.Message.get -> string +Microsoft.CodeAnalysis.Testing.DiagnosticResult.MessageArguments.get -> object[] +Microsoft.CodeAnalysis.Testing.DiagnosticResult.MessageFormat.get -> Microsoft.CodeAnalysis.LocalizableString +Microsoft.CodeAnalysis.Testing.DiagnosticResult.Options.get -> Microsoft.CodeAnalysis.Testing.DiagnosticOptions +Microsoft.CodeAnalysis.Testing.DiagnosticResult.Severity.get -> Microsoft.CodeAnalysis.DiagnosticSeverity +Microsoft.CodeAnalysis.Testing.DiagnosticResult.Spans.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithArguments(params object[] arguments) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithDefaultPath(string path) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithLineOffset(int offset) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithLocation(Microsoft.CodeAnalysis.Text.LinePosition location) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithLocation(int line, int column) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithLocation(int markupKey) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithLocation(int markupKey, Microsoft.CodeAnalysis.Testing.DiagnosticLocationOptions options) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithLocation(string path, Microsoft.CodeAnalysis.Text.LinePosition location) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithLocation(string path, Microsoft.CodeAnalysis.Text.LinePosition location, Microsoft.CodeAnalysis.Testing.DiagnosticLocationOptions options) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithLocation(string path, int line, int column) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithMessage(string message) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithMessageFormat(Microsoft.CodeAnalysis.LocalizableString messageFormat) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithNoLocation() -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithOptions(Microsoft.CodeAnalysis.Testing.DiagnosticOptions options) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithSeverity(Microsoft.CodeAnalysis.DiagnosticSeverity severity) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithSpan(Microsoft.CodeAnalysis.FileLinePositionSpan span) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithSpan(Microsoft.CodeAnalysis.FileLinePositionSpan span, Microsoft.CodeAnalysis.Testing.DiagnosticLocationOptions options) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithSpan(int startLine, int startColumn, int endLine, int endColumn) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.DiagnosticResult.WithSpan(string path, int startLine, int startColumn, int endLine, int endColumn) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer +Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer.EmptyDiagnosticAnalyzer() -> void +Microsoft.CodeAnalysis.Testing.IVerifier +Microsoft.CodeAnalysis.Testing.IVerifier.Empty(string collectionName, System.Collections.Generic.IEnumerable collection) -> void +Microsoft.CodeAnalysis.Testing.IVerifier.Equal(T expected, T actual, string message = null) -> void +Microsoft.CodeAnalysis.Testing.IVerifier.Fail(string message = null) -> void +Microsoft.CodeAnalysis.Testing.IVerifier.False(bool assert, string message = null) -> void +Microsoft.CodeAnalysis.Testing.IVerifier.LanguageIsSupported(string language) -> void +Microsoft.CodeAnalysis.Testing.IVerifier.NotEmpty(string collectionName, System.Collections.Generic.IEnumerable collection) -> void +Microsoft.CodeAnalysis.Testing.IVerifier.PushContext(string context) -> Microsoft.CodeAnalysis.Testing.IVerifier +Microsoft.CodeAnalysis.Testing.IVerifier.SequenceEqual(System.Collections.Generic.IEnumerable expected, System.Collections.Generic.IEnumerable actual, System.Collections.Generic.IEqualityComparer equalityComparer = null, string message = null) -> void +Microsoft.CodeAnalysis.Testing.IVerifier.True(bool assert, string message = null) -> void +Microsoft.CodeAnalysis.Testing.IVerifierExtensions +Microsoft.CodeAnalysis.Testing.MarkupMode +Microsoft.CodeAnalysis.Testing.MarkupMode.Allow = 3 -> Microsoft.CodeAnalysis.Testing.MarkupMode +Microsoft.CodeAnalysis.Testing.MarkupMode.Ignore = 1 -> Microsoft.CodeAnalysis.Testing.MarkupMode +Microsoft.CodeAnalysis.Testing.MarkupMode.IgnoreFixable = 2 -> Microsoft.CodeAnalysis.Testing.MarkupMode +Microsoft.CodeAnalysis.Testing.MarkupMode.None = 0 -> Microsoft.CodeAnalysis.Testing.MarkupMode +Microsoft.CodeAnalysis.Testing.MarkupOptions +Microsoft.CodeAnalysis.Testing.MarkupOptions.None = 0 -> Microsoft.CodeAnalysis.Testing.MarkupOptions +Microsoft.CodeAnalysis.Testing.MarkupOptions.UseFirstDescriptor = 1 -> Microsoft.CodeAnalysis.Testing.MarkupOptions +Microsoft.CodeAnalysis.Testing.MetadataReferenceCollection +Microsoft.CodeAnalysis.Testing.MetadataReferenceCollection.Add(System.Reflection.Assembly assembly) -> void +Microsoft.CodeAnalysis.Testing.MetadataReferenceCollection.Add(string path) -> void +Microsoft.CodeAnalysis.Testing.MetadataReferenceCollection.MetadataReferenceCollection() -> void +Microsoft.CodeAnalysis.Testing.MetadataReferences +Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState +Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.AdditionalDiagnostics.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.AdditionalFiles.get -> System.Collections.Immutable.ImmutableArray<(string filename, Microsoft.CodeAnalysis.Text.SourceText content)> +Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.AdditionalProjectReferences.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.AdditionalReferences.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.AnalyzerConfigFiles.get -> System.Collections.Immutable.ImmutableArray<(string filename, Microsoft.CodeAnalysis.Text.SourceText content)> +Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.AssemblyName.get -> string +Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.DocumentationMode.get -> Microsoft.CodeAnalysis.DocumentationMode +Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.EvaluatedProjectState(Microsoft.CodeAnalysis.Testing.ProjectState state, Microsoft.CodeAnalysis.Testing.ReferenceAssemblies defaultReferenceAssemblies) -> void +Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.GeneratedSources.get -> System.Collections.Immutable.ImmutableArray<(string filename, Microsoft.CodeAnalysis.Text.SourceText content)> +Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.Language.get -> string +Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.Name.get -> string +Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.OutputKind.get -> Microsoft.CodeAnalysis.OutputKind +Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.ReferenceAssemblies.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.Sources.get -> System.Collections.Immutable.ImmutableArray<(string filename, Microsoft.CodeAnalysis.Text.SourceText content)> +Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.WithAdditionalDiagnostics(System.Collections.Immutable.ImmutableArray additionalDiagnostics) -> Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState +Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.WithSources(System.Collections.Immutable.ImmutableArray<(string filename, Microsoft.CodeAnalysis.Text.SourceText content)> sources) -> Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState +Microsoft.CodeAnalysis.Testing.PackageIdentity +Microsoft.CodeAnalysis.Testing.PackageIdentity.Id.get -> string +Microsoft.CodeAnalysis.Testing.PackageIdentity.PackageIdentity(string id, string version) -> void +Microsoft.CodeAnalysis.Testing.PackageIdentity.Version.get -> string +Microsoft.CodeAnalysis.Testing.ProjectCollection +Microsoft.CodeAnalysis.Testing.ProjectCollection.ProjectCollection(string defaultLanguage, string defaultExtension) -> void +Microsoft.CodeAnalysis.Testing.ProjectCollection.this[string projectName, string language].get -> Microsoft.CodeAnalysis.Testing.ProjectState +Microsoft.CodeAnalysis.Testing.ProjectCollection.this[string projectName].get -> Microsoft.CodeAnalysis.Testing.ProjectState +Microsoft.CodeAnalysis.Testing.ProjectState +Microsoft.CodeAnalysis.Testing.ProjectState.AdditionalFiles.get -> Microsoft.CodeAnalysis.Testing.SourceFileCollection +Microsoft.CodeAnalysis.Testing.ProjectState.AdditionalFilesFactories.get -> System.Collections.Generic.List>> +Microsoft.CodeAnalysis.Testing.ProjectState.AdditionalProjectReferences.get -> System.Collections.Generic.List +Microsoft.CodeAnalysis.Testing.ProjectState.AdditionalReferences.get -> Microsoft.CodeAnalysis.Testing.MetadataReferenceCollection +Microsoft.CodeAnalysis.Testing.ProjectState.AnalyzerConfigFiles.get -> Microsoft.CodeAnalysis.Testing.SourceFileCollection +Microsoft.CodeAnalysis.Testing.ProjectState.AssemblyName.get -> string +Microsoft.CodeAnalysis.Testing.ProjectState.DocumentationMode.get -> Microsoft.CodeAnalysis.DocumentationMode? +Microsoft.CodeAnalysis.Testing.ProjectState.DocumentationMode.set -> void +Microsoft.CodeAnalysis.Testing.ProjectState.GeneratedSources.get -> Microsoft.CodeAnalysis.Testing.SourceFileCollection +Microsoft.CodeAnalysis.Testing.ProjectState.Language.get -> string +Microsoft.CodeAnalysis.Testing.ProjectState.Name.get -> string +Microsoft.CodeAnalysis.Testing.ProjectState.OutputKind.get -> Microsoft.CodeAnalysis.OutputKind? +Microsoft.CodeAnalysis.Testing.ProjectState.OutputKind.set -> void +Microsoft.CodeAnalysis.Testing.ProjectState.ProjectState(string name, string language, string defaultPrefix, string defaultExtension) -> void +Microsoft.CodeAnalysis.Testing.ProjectState.ReferenceAssemblies.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +Microsoft.CodeAnalysis.Testing.ProjectState.ReferenceAssemblies.set -> void +Microsoft.CodeAnalysis.Testing.ProjectState.Sources.get -> Microsoft.CodeAnalysis.Testing.SourceFileList +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.AddAssemblies(System.Collections.Immutable.ImmutableArray assemblies) -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.AddFacadeAssemblies(System.Collections.Immutable.ImmutableArray facadeAssemblies) -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.AddLanguageSpecificAssemblies(string language, System.Collections.Immutable.ImmutableArray assemblies) -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.AddPackages(System.Collections.Immutable.ImmutableArray packages) -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Assemblies.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.AssemblyIdentityComparer.get -> Microsoft.CodeAnalysis.AssemblyIdentityComparer +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.FacadeAssemblies.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.LanguageSpecificAssemblies.get -> System.Collections.Immutable.ImmutableDictionary> +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Net +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetCore +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net20 +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net40 +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net45 +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net451 +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net452 +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net46 +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net461 +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net462 +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net47 +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net471 +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net472 +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net48 +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetStandard +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Packages.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.ReferenceAssemblies(string targetFramework) -> void +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.ReferenceAssemblies(string targetFramework, Microsoft.CodeAnalysis.Testing.PackageIdentity referenceAssemblyPackage, string referenceAssemblyPath) -> void +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.ReferenceAssemblyPackage.get -> Microsoft.CodeAnalysis.Testing.PackageIdentity +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.ReferenceAssemblyPath.get -> string +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.ResolveAsync(string language, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.TargetFramework.get -> string +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.WithAssemblies(System.Collections.Immutable.ImmutableArray assemblies) -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.WithAssemblyIdentityComparer(Microsoft.CodeAnalysis.AssemblyIdentityComparer assemblyIdentityComparer) -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.WithFacadeAssemblies(System.Collections.Immutable.ImmutableArray facadeAssemblies) -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.WithLanguageSpecificAssemblies(System.Collections.Immutable.ImmutableDictionary> languageSpecificAssemblies) -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.WithLanguageSpecificAssemblies(string language, System.Collections.Immutable.ImmutableArray assemblies) -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.WithPackages(System.Collections.Immutable.ImmutableArray packages) -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +Microsoft.CodeAnalysis.Testing.SolutionState +Microsoft.CodeAnalysis.Testing.SolutionState.AdditionalProjects.get -> Microsoft.CodeAnalysis.Testing.ProjectCollection +Microsoft.CodeAnalysis.Testing.SolutionState.ExpectedDiagnostics.get -> System.Collections.Generic.List +Microsoft.CodeAnalysis.Testing.SolutionState.InheritanceMode.get -> Microsoft.CodeAnalysis.Testing.StateInheritanceMode? +Microsoft.CodeAnalysis.Testing.SolutionState.InheritanceMode.set -> void +Microsoft.CodeAnalysis.Testing.SolutionState.MarkupHandling.get -> Microsoft.CodeAnalysis.Testing.MarkupMode? +Microsoft.CodeAnalysis.Testing.SolutionState.MarkupHandling.set -> void +Microsoft.CodeAnalysis.Testing.SolutionState.SolutionState(string name, string language, string defaultPrefix, string defaultExtension) -> void +Microsoft.CodeAnalysis.Testing.SolutionState.WithInheritedValuesApplied(Microsoft.CodeAnalysis.Testing.SolutionState baseState, System.Collections.Immutable.ImmutableArray fixableDiagnostics) -> Microsoft.CodeAnalysis.Testing.SolutionState +Microsoft.CodeAnalysis.Testing.SolutionState.WithProcessedMarkup(Microsoft.CodeAnalysis.Testing.MarkupOptions markupOptions, Microsoft.CodeAnalysis.DiagnosticDescriptor defaultDiagnostic, System.Collections.Immutable.ImmutableArray supportedDiagnostics, System.Collections.Immutable.ImmutableArray fixableDiagnostics, string defaultPath) -> Microsoft.CodeAnalysis.Testing.SolutionState +Microsoft.CodeAnalysis.Testing.SourceFileCollection +Microsoft.CodeAnalysis.Testing.SourceFileCollection.Add((System.Type sourceGeneratorType, string filename, Microsoft.CodeAnalysis.Text.SourceText content) file) -> void +Microsoft.CodeAnalysis.Testing.SourceFileCollection.Add((System.Type sourceGeneratorType, string filename, string content) file) -> void +Microsoft.CodeAnalysis.Testing.SourceFileCollection.Add((string filename, string content) file) -> void +Microsoft.CodeAnalysis.Testing.SourceFileCollection.SourceFileCollection() -> void +Microsoft.CodeAnalysis.Testing.SourceFileList +Microsoft.CodeAnalysis.Testing.SourceFileList.Add(Microsoft.CodeAnalysis.Text.SourceText content) -> void +Microsoft.CodeAnalysis.Testing.SourceFileList.Add(string content) -> void +Microsoft.CodeAnalysis.Testing.SourceFileList.SourceFileList(string defaultPrefix, string defaultExtension) -> void +Microsoft.CodeAnalysis.Testing.StateInheritanceMode +Microsoft.CodeAnalysis.Testing.StateInheritanceMode.AutoInherit = 0 -> Microsoft.CodeAnalysis.Testing.StateInheritanceMode +Microsoft.CodeAnalysis.Testing.StateInheritanceMode.AutoInheritAll = 2 -> Microsoft.CodeAnalysis.Testing.StateInheritanceMode +Microsoft.CodeAnalysis.Testing.StateInheritanceMode.Explicit = 1 -> Microsoft.CodeAnalysis.Testing.StateInheritanceMode +Microsoft.CodeAnalysis.Testing.TestBehaviors +Microsoft.CodeAnalysis.Testing.TestBehaviors.None = 0 -> Microsoft.CodeAnalysis.Testing.TestBehaviors +Microsoft.CodeAnalysis.Testing.TestBehaviors.SkipGeneratedCodeCheck = 1 -> Microsoft.CodeAnalysis.Testing.TestBehaviors +Microsoft.CodeAnalysis.Testing.TestBehaviors.SkipGeneratedSourcesCheck = 4 -> Microsoft.CodeAnalysis.Testing.TestBehaviors +Microsoft.CodeAnalysis.Testing.TestBehaviors.SkipSuppressionCheck = 2 -> Microsoft.CodeAnalysis.Testing.TestBehaviors +Microsoft.CodeAnalysis.Testing.TestFileMarkupParser +abstract Microsoft.CodeAnalysis.Testing.AnalyzerTest.CreateCompilationOptions() -> Microsoft.CodeAnalysis.CompilationOptions +abstract Microsoft.CodeAnalysis.Testing.AnalyzerTest.CreateParseOptions() -> Microsoft.CodeAnalysis.ParseOptions +abstract Microsoft.CodeAnalysis.Testing.AnalyzerTest.DefaultFileExt.get -> string +abstract Microsoft.CodeAnalysis.Testing.AnalyzerTest.GetDiagnosticAnalyzers() -> System.Collections.Generic.IEnumerable +abstract Microsoft.CodeAnalysis.Testing.AnalyzerTest.Language.get -> string +abstract Microsoft.CodeAnalysis.Testing.CodeActionTest.SyntaxKindType.get -> System.Type +override Microsoft.CodeAnalysis.Testing.DiagnosticResult.ToString() -> string +override Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer.Initialize(Microsoft.CodeAnalysis.Diagnostics.AnalysisContext context) -> void +override Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer.SupportedDiagnostics.get -> System.Collections.Immutable.ImmutableArray +static Microsoft.CodeAnalysis.Testing.AnalyzerTest.Verify.get -> TVerifier +static Microsoft.CodeAnalysis.Testing.AnalyzerVerifier.Diagnostic() -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +static Microsoft.CodeAnalysis.Testing.AnalyzerVerifier.Diagnostic(Microsoft.CodeAnalysis.DiagnosticDescriptor descriptor) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +static Microsoft.CodeAnalysis.Testing.AnalyzerVerifier.Diagnostic(string diagnosticId) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +static Microsoft.CodeAnalysis.Testing.AnalyzerVerifier.VerifyAnalyzerAsync(string source, params Microsoft.CodeAnalysis.Testing.DiagnosticResult[] expected) -> System.Threading.Tasks.Task +static Microsoft.CodeAnalysis.Testing.CodeActionTest.CodeActionExpected(Microsoft.CodeAnalysis.Testing.SolutionState state) -> bool +static Microsoft.CodeAnalysis.Testing.CodeActionTest.HasAnyChange(Microsoft.CodeAnalysis.Testing.ProjectState oldState, Microsoft.CodeAnalysis.Testing.ProjectState newState, bool recursive) -> bool +static Microsoft.CodeAnalysis.Testing.CodeActionTest.TryGetCodeActionToApply(int iteration, System.Collections.Immutable.ImmutableArray actions, int? codeActionIndex, string codeActionEquivalenceKey, System.Action codeActionVerifier, Microsoft.CodeAnalysis.Testing.IVerifier verifier) -> Microsoft.CodeAnalysis.CodeActions.CodeAction +static Microsoft.CodeAnalysis.Testing.DiagnosticResult.CompilerError(string identifier) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +static Microsoft.CodeAnalysis.Testing.DiagnosticResult.CompilerWarning(string identifier) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +static Microsoft.CodeAnalysis.Testing.IVerifierExtensions.EqualOrDiff(this Microsoft.CodeAnalysis.Testing.IVerifier verifier, string expected, string actual, string message = null) -> void +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Default.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Net.Net50.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Net.Net60.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetCore.NetCoreApp10.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetCore.NetCoreApp11.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetCore.NetCoreApp20.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetCore.NetCoreApp21.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetCore.NetCoreApp30.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetCore.NetCoreApp31.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net20.Default.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net20.WindowsForms.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net40.Default.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net40.WindowsForms.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net40.Wpf.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net45.Default.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net45.WindowsForms.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net45.Wpf.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net451.Default.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net451.WindowsForms.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net451.Wpf.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net452.Default.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net452.WindowsForms.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net452.Wpf.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net46.Default.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net46.WindowsForms.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net46.Wpf.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net461.Default.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net461.WindowsForms.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net461.Wpf.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net462.Default.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net462.WindowsForms.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net462.Wpf.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net47.Default.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net47.WindowsForms.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net47.Wpf.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net471.Default.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net471.WindowsForms.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net471.Wpf.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net472.Default.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net472.WindowsForms.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net472.Wpf.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net48.Default.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net48.WindowsForms.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetFramework.Net48.Wpf.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetStandard.NetStandard10.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetStandard.NetStandard11.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetStandard.NetStandard12.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetStandard.NetStandard13.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetStandard.NetStandard14.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetStandard.NetStandard15.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetStandard.NetStandard16.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetStandard.NetStandard20.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NetStandard.NetStandard21.get -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies +static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.CreateTestFile(string code, System.Collections.Immutable.ImmutableArray positions, System.Collections.Immutable.ImmutableDictionary> spans) -> string +static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.CreateTestFile(string code, int position) -> string +static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.CreateTestFile(string code, int? position, System.Collections.Immutable.ImmutableArray spans) -> string +static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.CreateTestFile(string code, int? position, System.Collections.Immutable.ImmutableDictionary> spans) -> string +static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetPosition(string input, out string output, out int cursorPosition) -> void +static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetPositionAndSpan(string input, out string output, out int cursorPosition, out Microsoft.CodeAnalysis.Text.TextSpan span) -> void +static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetPositionAndSpans(string input, out int? cursorPosition, out System.Collections.Immutable.ImmutableArray spans) -> void +static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetPositionAndSpans(string input, out int? cursorPosition, out System.Collections.Immutable.ImmutableDictionary> spans) -> void +static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetPositionAndSpans(string input, out string output, out int cursorPosition, out System.Collections.Immutable.ImmutableArray spans) -> void +static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetPositionAndSpans(string input, out string output, out int cursorPosition, out System.Collections.Immutable.ImmutableDictionary> spans) -> void +static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetPositionAndSpans(string input, out string output, out int? cursorPosition, out System.Collections.Immutable.ImmutableArray spans) -> void +static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetPositionAndSpans(string input, out string output, out int? cursorPosition, out System.Collections.Immutable.ImmutableDictionary> spans) -> void +static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetPositionsAndSpans(string input, out string output, out System.Collections.Immutable.ImmutableArray positions, out System.Collections.Immutable.ImmutableDictionary> spans) -> void +static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetSpan(string input, out string output, out Microsoft.CodeAnalysis.Text.TextSpan span) -> void +static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetSpans(string input, out string output, out System.Collections.Immutable.ImmutableArray spans) -> void +static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetSpans(string input, out string output, out System.Collections.Immutable.ImmutableDictionary> spans) -> void +static readonly Microsoft.CodeAnalysis.Testing.DiagnosticResult.EmptyDiagnosticResults -> Microsoft.CodeAnalysis.Testing.DiagnosticResult[] +virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest.ApplyCompilationOptions(Microsoft.CodeAnalysis.Project project) -> Microsoft.CodeAnalysis.Project +virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest.CreateProjectImplAsync(Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState primaryProject, System.Collections.Immutable.ImmutableArray additionalProjects, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest.CreateSolutionAsync(Microsoft.CodeAnalysis.ProjectId projectId, Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState projectState, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest.CreateWorkspaceImpl() -> Microsoft.CodeAnalysis.Workspace +virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest.DefaultFilePath.get -> string +virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest.DefaultFilePathPrefix.get -> string +virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest.DefaultTestProjectName.get -> string +virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest.GetAnalyzerOptions(Microsoft.CodeAnalysis.Project project) -> Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions +virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest.GetDefaultDiagnostic(Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer[] analyzers) -> Microsoft.CodeAnalysis.DiagnosticDescriptor +virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest.GetProjectCompilationAsync(Microsoft.CodeAnalysis.Project project, Microsoft.CodeAnalysis.Testing.IVerifier verifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest.IsCompilerDiagnosticIncluded(Microsoft.CodeAnalysis.Diagnostic diagnostic, Microsoft.CodeAnalysis.Testing.CompilerDiagnostics compilerDiagnostics) -> bool +virtual Microsoft.CodeAnalysis.Testing.AnalyzerTest.RunImplAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +virtual Microsoft.CodeAnalysis.Testing.CodeActionTest.FilterCodeActions(System.Collections.Immutable.ImmutableArray actions) -> System.Collections.Immutable.ImmutableArray +virtual Microsoft.CodeAnalysis.Testing.DefaultVerifier.CreateMessage(string message) -> string +virtual Microsoft.CodeAnalysis.Testing.DefaultVerifier.Empty(string collectionName, System.Collections.Generic.IEnumerable collection) -> void +virtual Microsoft.CodeAnalysis.Testing.DefaultVerifier.Equal(T expected, T actual, string message = null) -> void +virtual Microsoft.CodeAnalysis.Testing.DefaultVerifier.Fail(string message = null) -> void +virtual Microsoft.CodeAnalysis.Testing.DefaultVerifier.False(bool assert, string message = null) -> void +virtual Microsoft.CodeAnalysis.Testing.DefaultVerifier.LanguageIsSupported(string language) -> void +virtual Microsoft.CodeAnalysis.Testing.DefaultVerifier.NotEmpty(string collectionName, System.Collections.Generic.IEnumerable collection) -> void +virtual Microsoft.CodeAnalysis.Testing.DefaultVerifier.PushContext(string context) -> Microsoft.CodeAnalysis.Testing.IVerifier +virtual Microsoft.CodeAnalysis.Testing.DefaultVerifier.SequenceEqual(System.Collections.Generic.IEnumerable expected, System.Collections.Generic.IEnumerable actual, System.Collections.Generic.IEqualityComparer equalityComparer = null, string message = null) -> void +virtual Microsoft.CodeAnalysis.Testing.DefaultVerifier.True(bool assert, string message = null) -> void +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.NuGetConfigFilePath.get -> string +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.WithNuGetConfigFilePath(string nugetConfigFilePath) -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ReferenceAssemblies+FileSystemSemaphore.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ReferenceAssemblies+FileSystemSemaphore.cs new file mode 100644 index 000000000..a5ee01291 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ReferenceAssemblies+FileSystemSemaphore.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.Testing +{ + public sealed partial class ReferenceAssemblies + { + private sealed class FileSystemSemaphore + { + private readonly string _path; + + public FileSystemSemaphore(string path) + { + _path = path ?? throw new ArgumentNullException(nameof(path)); + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + } + + internal async Task WaitAsync(CancellationToken cancellationToken) + { + while (true) + { + try + { + return new Releaser(File.Open(_path, FileMode.OpenOrCreate, FileAccess.Read, FileShare.None)); + } + catch (IOException) + { + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); + } + } + } + + public readonly struct Releaser : IDisposable + { + private readonly FileStream _fileStream; + + public Releaser(FileStream fileStream) + { + _fileStream = fileStream; + } + + public void Dispose() + { + _fileStream?.Dispose(); + } + } + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ReferenceAssemblies.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ReferenceAssemblies.cs new file mode 100644 index 000000000..2ab950247 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ReferenceAssemblies.cs @@ -0,0 +1,1060 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Common; +using NuGet.Configuration; +using NuGet.Frameworks; +using NuGet.Packaging; +using NuGet.Packaging.Core; +using NuGet.Protocol; +using NuGet.Protocol.Core.Types; +using NuGet.Resolver; + +#if NET46 || NET472 || NETSTANDARD || NETCOREAPP3_1 +using NuGet.Packaging.Signing; +#endif + +namespace Microsoft.CodeAnalysis.Testing +{ + public sealed partial class ReferenceAssemblies + { + private const string ReferenceAssembliesPackageVersion = "1.0.0"; + + private static readonly FileSystemSemaphore Semaphore = new FileSystemSemaphore(Path.Combine(Path.GetTempPath(), "test-packages", ".lock")); + + private static ImmutableDictionary s_packageToInstalledLocation + = ImmutableDictionary.Create(PackageIdentityComparer.Default); + + private static ImmutableHashSet s_emptyPackages + = ImmutableHashSet.Create(PackageIdentityComparer.Default); + + private readonly Dictionary> _references + = new Dictionary>(); + + public ReferenceAssemblies(string targetFramework) + { + TargetFramework = targetFramework ?? throw new ArgumentNullException(nameof(targetFramework)); + AssemblyIdentityComparer = AssemblyIdentityComparer.Default; + ReferenceAssemblyPath = null; + Assemblies = ImmutableArray.Empty; + FacadeAssemblies = ImmutableArray.Empty; + LanguageSpecificAssemblies = ImmutableDictionary>.Empty; + Packages = ImmutableArray.Empty; + } + + public ReferenceAssemblies(string targetFramework, PackageIdentity? referenceAssemblyPackage, string referenceAssemblyPath) + { + TargetFramework = targetFramework ?? throw new ArgumentNullException(nameof(targetFramework)); + AssemblyIdentityComparer = AssemblyIdentityComparer.Default; + ReferenceAssemblyPackage = referenceAssemblyPackage ?? throw new ArgumentNullException(nameof(referenceAssemblyPackage)); + ReferenceAssemblyPath = referenceAssemblyPath; + Assemblies = ImmutableArray.Empty; + FacadeAssemblies = ImmutableArray.Empty; + LanguageSpecificAssemblies = ImmutableDictionary>.Empty; + Packages = ImmutableArray.Empty; + } + + private ReferenceAssemblies( + string targetFramework, + AssemblyIdentityComparer assemblyIdentityComparer, + PackageIdentity? referenceAssemblyPackage, + string? referenceAssemblyPath, + ImmutableArray assemblies, + ImmutableArray facadeAssemblies, + ImmutableDictionary> languageSpecificAssemblies, + ImmutableArray packages, + string? nugetConfigFilePath) + { + TargetFramework = targetFramework; + AssemblyIdentityComparer = assemblyIdentityComparer; + ReferenceAssemblyPackage = referenceAssemblyPackage; + ReferenceAssemblyPath = referenceAssemblyPath; + Assemblies = assemblies.IsDefault ? ImmutableArray.Empty : assemblies; + FacadeAssemblies = facadeAssemblies.IsDefault ? ImmutableArray.Empty : facadeAssemblies; + LanguageSpecificAssemblies = languageSpecificAssemblies; + Packages = packages.IsDefault ? ImmutableArray.Empty : packages; + NuGetConfigFilePath = nugetConfigFilePath; + } + + public static ReferenceAssemblies Default + { + get + { +#if NETSTANDARD1_5 + return NetStandard.NetStandard15; +#elif NETSTANDARD2_0 + return NetStandard.NetStandard20; +#elif NET452 + return NetFramework.Net452.Default; +#elif NET46 + return NetFramework.Net46.Default; +#elif NET472 + return NetFramework.Net472.Default; +#elif NETCOREAPP3_1 + return NetCore.NetCoreApp31; +#endif + } + } + + public string TargetFramework { get; } + + public AssemblyIdentityComparer AssemblyIdentityComparer { get; } + + public PackageIdentity? ReferenceAssemblyPackage { get; } + + public string? ReferenceAssemblyPath { get; } + + public ImmutableArray Assemblies { get; } + + public ImmutableArray FacadeAssemblies { get; } + + public ImmutableDictionary> LanguageSpecificAssemblies { get; } + + public ImmutableArray Packages { get; } + + public string? NuGetConfigFilePath { get; } + + public ReferenceAssemblies WithAssemblyIdentityComparer(AssemblyIdentityComparer assemblyIdentityComparer) + => new ReferenceAssemblies(TargetFramework, assemblyIdentityComparer, ReferenceAssemblyPackage, ReferenceAssemblyPath, Assemblies, FacadeAssemblies, LanguageSpecificAssemblies, Packages, NuGetConfigFilePath); + + public ReferenceAssemblies WithAssemblies(ImmutableArray assemblies) + => new ReferenceAssemblies(TargetFramework, AssemblyIdentityComparer, ReferenceAssemblyPackage, ReferenceAssemblyPath, assemblies, FacadeAssemblies, LanguageSpecificAssemblies, Packages, NuGetConfigFilePath); + + public ReferenceAssemblies WithFacadeAssemblies(ImmutableArray facadeAssemblies) + => new ReferenceAssemblies(TargetFramework, AssemblyIdentityComparer, ReferenceAssemblyPackage, ReferenceAssemblyPath, Assemblies, facadeAssemblies, LanguageSpecificAssemblies, Packages, NuGetConfigFilePath); + + public ReferenceAssemblies AddAssemblies(ImmutableArray assemblies) + => WithAssemblies(Assemblies.AddRange(assemblies)); + + public ReferenceAssemblies AddFacadeAssemblies(ImmutableArray facadeAssemblies) + => WithFacadeAssemblies(FacadeAssemblies.AddRange(facadeAssemblies)); + + public ReferenceAssemblies WithLanguageSpecificAssemblies(ImmutableDictionary> languageSpecificAssemblies) + => new ReferenceAssemblies(TargetFramework, AssemblyIdentityComparer, ReferenceAssemblyPackage, ReferenceAssemblyPath, Assemblies, FacadeAssemblies, languageSpecificAssemblies, Packages, NuGetConfigFilePath); + + public ReferenceAssemblies WithLanguageSpecificAssemblies(string language, ImmutableArray assemblies) + => WithLanguageSpecificAssemblies(LanguageSpecificAssemblies.SetItem(language, assemblies)); + + public ReferenceAssemblies AddLanguageSpecificAssemblies(string language, ImmutableArray assemblies) + { + if (!LanguageSpecificAssemblies.TryGetValue(language, out var existing)) + { + existing = ImmutableArray.Empty; + } + + return WithLanguageSpecificAssemblies(language, existing.AddRange(assemblies)); + } + + public ReferenceAssemblies WithPackages(ImmutableArray packages) + => new ReferenceAssemblies(TargetFramework, AssemblyIdentityComparer, ReferenceAssemblyPackage, ReferenceAssemblyPath, Assemblies, FacadeAssemblies, LanguageSpecificAssemblies, packages, NuGetConfigFilePath); + + public ReferenceAssemblies AddPackages(ImmutableArray packages) + => WithPackages(Packages.AddRange(packages)); + + public ReferenceAssemblies WithNuGetConfigFilePath(string nugetConfigFilePath) + => new ReferenceAssemblies(TargetFramework, AssemblyIdentityComparer, ReferenceAssemblyPackage, ReferenceAssemblyPath, Assemblies, FacadeAssemblies, LanguageSpecificAssemblies, Packages, nugetConfigFilePath); + + public async Task> ResolveAsync(string? language, CancellationToken cancellationToken) + { + if (language is object) + { + if (LanguageSpecificAssemblies.IsEmpty + || !LanguageSpecificAssemblies.TryGetValue(language, out var languageSpecificAssemblies) + || languageSpecificAssemblies.IsEmpty) + { + return await ResolveAsync(null, cancellationToken); + } + } + + language ??= string.Empty; + lock (_references) + { + if (_references.TryGetValue(language, out var references)) + { + return references; + } + } + + using (var releaser = await Semaphore.WaitAsync(cancellationToken)) + { + lock (_references) + { + if (_references.TryGetValue(language, out var references)) + { + return references; + } + } + + var computedReferences = await ResolveCoreAsync(language, cancellationToken); + lock (_references) + { + _references.Add(language, computedReferences); + } + + return computedReferences; + } + } + + /// + private async Task> ResolveCoreAsync(string language, CancellationToken cancellationToken) + { + var settings = string.IsNullOrEmpty(NuGetConfigFilePath) ? Settings.LoadDefaultSettings(root: null) : Settings.LoadSpecificSettings(root: null, NuGetConfigFilePath); + var sourceRepositoryProvider = new SourceRepositoryProvider(new PackageSourceProvider(settings), Repository.Provider.GetCoreV3()); + var targetFramework = NuGetFramework.ParseFolder(TargetFramework); + var logger = NullLogger.Instance; + + using (var cacheContext = new SourceCacheContext()) + { + var temporaryPackagesFolder = Path.Combine(Path.GetTempPath(), "test-packages"); + Directory.CreateDirectory(temporaryPackagesFolder); + + var repositories = sourceRepositoryProvider.GetRepositories().ToImmutableArray(); + repositories = repositories.Insert(0, new SourceRepository(new PackageSource(temporaryPackagesFolder, "test-packages"), Repository.Provider.GetCoreV3(), FeedType.FileSystemPackagesConfig)); + repositories = repositories.Insert(0, sourceRepositoryProvider.CreateRepository(new PackageSource(new Uri(SettingsUtility.GetGlobalPackagesFolder(settings)).AbsoluteUri, "global"), FeedType.FileSystemV3)); + var dependencies = ImmutableDictionary.CreateBuilder(PackageIdentityComparer.Default); + + if (ReferenceAssemblyPackage is object) + { + await GetPackageDependenciesAsync(ReferenceAssemblyPackage.ToNuGetIdentity(), targetFramework, repositories, cacheContext, logger, dependencies, cancellationToken); + } + + foreach (var packageIdentity in Packages) + { + await GetPackageDependenciesAsync(packageIdentity.ToNuGetIdentity(), targetFramework, repositories, cacheContext, logger, dependencies, cancellationToken); + } + + var availablePackages = dependencies.ToImmutable(); + + var packagesToInstall = new List(); + if (ReferenceAssemblyPackage is object) + { + packagesToInstall.Add(ReferenceAssemblyPackage.ToNuGetIdentity()!); + } + + if (!Packages.IsEmpty) + { + var targetIds = new List(Packages.Select(package => package.Id)); + var preferredVersions = new List(Packages.Select(package => package.ToNuGetIdentity())); + if (ReferenceAssemblyPackage is object) + { + // Make sure to include the implicit reference assembly package + if (!targetIds.Contains(ReferenceAssemblyPackage.Id)) + { + targetIds.Insert(0, ReferenceAssemblyPackage.Id); + } + + if (!preferredVersions.Any(preferred => preferred.Id == ReferenceAssemblyPackage.Id)) + { + preferredVersions.Add(ReferenceAssemblyPackage.ToNuGetIdentity()); + } + } + + var resolverContext = new PackageResolverContext( + DependencyBehavior.Lowest, + targetIds, + Enumerable.Empty(), + Enumerable.Empty(), + preferredVersions, + availablePackages.Values, + repositories.Select(repository => repository.PackageSource), + logger); + var resolver = new PackageResolver(); + + packagesToInstall.AddRange(resolver.Resolve(resolverContext, cancellationToken)); + } + + var globalPathResolver = new PackagePathResolver(SettingsUtility.GetGlobalPackagesFolder(settings)); + var localPathResolver = new PackagePathResolver(temporaryPackagesFolder); +#if NET452 + var packageExtractionContext = new PackageExtractionContext(logger) + { + PackageSaveMode = PackageSaveMode.Defaultv3, + XmlDocFileSaveMode = XmlDocFileSaveMode.None, + }; +#elif NET46 || NET472 || NETSTANDARD2_0 || NETCOREAPP3_1 + var packageExtractionContext = new PackageExtractionContext( + PackageSaveMode.Defaultv3, + XmlDocFileSaveMode.None, + ClientPolicyContext.GetClientPolicy(settings, logger), + logger); +#elif NETSTANDARD1_5 + var packageExtractionContext = new PackageExtractionContext( + PackageSaveMode.Defaultv3, + XmlDocFileSaveMode.None, + logger, + new PackageSignatureVerifier( + SignatureVerificationProviderFactory.GetSignatureVerificationProviders(), + SignedPackageVerifierSettings.Default)); +#else +#error The current target framework is not supported. +#endif + + var frameworkReducer = new FrameworkReducer(); + + var frameworkAssemblies = new HashSet(); + frameworkAssemblies.UnionWith(Assemblies); + if (LanguageSpecificAssemblies.TryGetValue(language, out var languageSpecificAssemblies)) + { + frameworkAssemblies.UnionWith(languageSpecificAssemblies); + } + + var resolvedAssemblies = new HashSet(); + foreach (var packageToInstall in packagesToInstall) + { + if (s_emptyPackages.Contains(packageToInstall)) + { + continue; + } + + PackageReaderBase packageReader; + var installedPath = GetInstalledPath(localPathResolver, globalPathResolver, packageToInstall); + if (installedPath is null) + { + var downloadResource = await availablePackages[packageToInstall].Source.GetResourceAsync(cancellationToken); + var downloadResult = await downloadResource.GetDownloadResourceResultAsync( + packageToInstall, + new PackageDownloadContext(cacheContext), + SettingsUtility.GetGlobalPackagesFolder(settings), + logger, + cancellationToken); + + if (!PackageIdentityComparer.Default.Equals(packageToInstall, ReferenceAssemblyPackage?.ToNuGetIdentity()) + && !downloadResult.PackageReader.GetItems(PackagingConstants.Folders.Lib).Any() + && !downloadResult.PackageReader.GetItems(PackagingConstants.Folders.Ref).Any()) + { + // This package has no compile time impact + ImmutableInterlocked.Update(ref s_emptyPackages, (emptyPackages, package) => emptyPackages.Add(package), packageToInstall); + continue; + } + + await PackageExtractor.ExtractPackageAsync( +#if !NET452 && !NETSTANDARD1_5 +#pragma warning disable SA1114 // Parameter list should follow declaration + downloadResult.PackageSource, +#pragma warning restore SA1114 // Parameter list should follow declaration +#endif + downloadResult.PackageStream, + localPathResolver, + packageExtractionContext, + cancellationToken); + + installedPath = GetInstalledPath(localPathResolver, globalPathResolver, packageToInstall); + packageReader = downloadResult.PackageReader; + } + else + { + packageReader = new PackageFolderReader(installedPath); + } + + if (installedPath is null) + { + continue; + } + + var libItems = await packageReader.GetLibItemsAsync(cancellationToken); + var nearestLib = frameworkReducer.GetNearest(targetFramework, libItems.Select(x => x.TargetFramework)); + var frameworkItems = await packageReader.GetFrameworkItemsAsync(cancellationToken); + var nearestFramework = frameworkReducer.GetNearest(targetFramework, frameworkItems.Select(x => x.TargetFramework)); + var refItems = await packageReader.GetItemsAsync(PackagingConstants.Folders.Ref, cancellationToken); + var nearestRef = frameworkReducer.GetNearest(targetFramework, refItems.Select(x => x.TargetFramework)); + if (nearestRef is object) + { + var nearestRefItems = refItems.Single(x => x.TargetFramework == nearestRef); + foreach (var item in nearestRefItems.Items) + { + if (!string.Equals(Path.GetExtension(item), ".dll", StringComparison.OrdinalIgnoreCase) + && !string.Equals(Path.GetExtension(item), ".exe", StringComparison.OrdinalIgnoreCase) + && !string.Equals(Path.GetExtension(item), ".winmd", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + resolvedAssemblies.Add(Path.Combine(installedPath, item)); + } + } + else if (nearestLib is object) + { + var nearestLibItems = libItems.Single(x => x.TargetFramework == nearestLib); + foreach (var item in nearestLibItems.Items) + { + if (!string.Equals(Path.GetExtension(item), ".dll", StringComparison.OrdinalIgnoreCase) + && !string.Equals(Path.GetExtension(item), ".exe", StringComparison.OrdinalIgnoreCase) + && !string.Equals(Path.GetExtension(item), ".winmd", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + resolvedAssemblies.Add(Path.Combine(installedPath, item)); + } + } + + // Include framework references except for package based frameworks + if (!targetFramework.IsPackageBased && nearestFramework is object) + { + var nearestFrameworkItems = frameworkItems.Single(x => x.TargetFramework == nearestFramework); + frameworkAssemblies.UnionWith(nearestFrameworkItems.Items); + } + } + + var referenceAssemblyInstalledPath = ReferenceAssemblyPackage is object + ? GetInstalledPath(localPathResolver, globalPathResolver, ReferenceAssemblyPackage.ToNuGetIdentity()) + : null; + Debug.Assert(ReferenceAssemblyPackage is null || referenceAssemblyInstalledPath is object, $"Assertion failed: {nameof(ReferenceAssemblyPackage)} is null || {nameof(referenceAssemblyInstalledPath)} is object"); + Debug.Assert(ReferenceAssemblyPackage is null || ReferenceAssemblyPath is object, $"Assertion failed: {nameof(ReferenceAssemblyPackage)} is null || {nameof(ReferenceAssemblyPath)} is object"); + + foreach (var assembly in frameworkAssemblies) + { + if (ReferenceAssemblyPackage is null) + { + throw new InvalidOperationException($"Cannot resolve assembly '{assembly}' without a reference assembly package"); + } + + if (File.Exists(Path.Combine(referenceAssemblyInstalledPath!, ReferenceAssemblyPath!, assembly + ".dll"))) + { + resolvedAssemblies.Add(Path.GetFullPath(Path.Combine(referenceAssemblyInstalledPath!, ReferenceAssemblyPath!, assembly + ".dll"))); + } + else if (File.Exists(Path.Combine(referenceAssemblyInstalledPath!, ReferenceAssemblyPath!, assembly + ".exe"))) + { + resolvedAssemblies.Add(Path.GetFullPath(Path.Combine(referenceAssemblyInstalledPath!, ReferenceAssemblyPath!, assembly + ".exe"))); + } + else if (File.Exists(Path.Combine(referenceAssemblyInstalledPath!, ReferenceAssemblyPath!, assembly + ".winmd"))) + { + resolvedAssemblies.Add(Path.GetFullPath(Path.Combine(referenceAssemblyInstalledPath!, ReferenceAssemblyPath!, assembly + ".winmd"))); + } + } + + // Prefer assemblies from the reference assembly package to ones otherwise provided + if (ReferenceAssemblyPackage is object) + { + var referenceAssemblies = new HashSet(resolvedAssemblies.Where(resolved => resolved.StartsWith(referenceAssemblyInstalledPath!))); + + // Suppression due to https://github.com/dotnet/roslyn/issues/44735 + var referenceAssemblyNames = new HashSet(referenceAssemblies.Select((Func)Path.GetFileNameWithoutExtension!)); + resolvedAssemblies.RemoveWhere(resolved => referenceAssemblyNames.Contains(Path.GetFileNameWithoutExtension(resolved)) && !referenceAssemblies.Contains(resolved)); + } + + // Add the facade assemblies + if (ReferenceAssemblyPackage is object) + { + var facadesPath = Path.Combine(referenceAssemblyInstalledPath!, ReferenceAssemblyPath!, "Facades"); + if (Directory.Exists(facadesPath)) + { + foreach (var path in Directory.GetFiles(facadesPath, "*.dll").Concat(Directory.GetFiles(facadesPath, "*.exe")).Concat(Directory.GetFiles(facadesPath, "*.winmd"))) + { + resolvedAssemblies.RemoveWhere(existingAssembly => Path.GetFileNameWithoutExtension(existingAssembly) == Path.GetFileNameWithoutExtension(path)); + resolvedAssemblies.Add(Path.GetFullPath(path)); + } + } + + foreach (var assembly in FacadeAssemblies) + { + if (File.Exists(Path.Combine(referenceAssemblyInstalledPath!, ReferenceAssemblyPath!, assembly + ".dll"))) + { + resolvedAssemblies.RemoveWhere(existingAssembly => Path.GetFileNameWithoutExtension(existingAssembly) == assembly); + resolvedAssemblies.Add(Path.GetFullPath(Path.Combine(referenceAssemblyInstalledPath!, ReferenceAssemblyPath!, assembly + ".dll"))); + } + else if (File.Exists(Path.Combine(referenceAssemblyInstalledPath!, ReferenceAssemblyPath!, assembly + ".exe"))) + { + resolvedAssemblies.RemoveWhere(existingAssembly => Path.GetFileNameWithoutExtension(existingAssembly) == assembly); + resolvedAssemblies.Add(Path.GetFullPath(Path.Combine(referenceAssemblyInstalledPath!, ReferenceAssemblyPath!, assembly + ".exe"))); + } + else if (File.Exists(Path.Combine(referenceAssemblyInstalledPath!, ReferenceAssemblyPath!, assembly + ".winmd"))) + { + resolvedAssemblies.RemoveWhere(existingAssembly => Path.GetFileNameWithoutExtension(existingAssembly) == assembly); + resolvedAssemblies.Add(Path.GetFullPath(Path.Combine(referenceAssemblyInstalledPath!, ReferenceAssemblyPath!, assembly + ".winmd"))); + } + } + } + else + { + if (!FacadeAssemblies.IsEmpty) + { + throw new InvalidOperationException($"Cannot resolve facade assemblies without a reference assembly package"); + } + } + + return resolvedAssemblies.Select(MetadataReferences.CreateReferenceFromFile).ToImmutableArray(); + + static string? GetInstalledPath(PackagePathResolver localPathResolver, PackagePathResolver globalPathResolver, NuGet.Packaging.Core.PackageIdentity packageIdentity) + { + string? installedPath = s_packageToInstalledLocation.GetValueOrDefault(packageIdentity); + if (installedPath is null) + { + installedPath = GetInstalledPath(localPathResolver, packageIdentity) + ?? GetInstalledPath(globalPathResolver, packageIdentity); + if (installedPath is object) + { + installedPath = ImmutableInterlocked.GetOrAdd(ref s_packageToInstalledLocation, packageIdentity, installedPath); + } + } + + return installedPath; + + static string? GetInstalledPath(PackagePathResolver resolver, NuGet.Packaging.Core.PackageIdentity id) + { + try + { + return resolver.GetInstalledPath(id); + } + catch (PathTooLongException) + { + return null; + } + } + } + } + } + + private static async Task GetPackageDependenciesAsync( + NuGet.Packaging.Core.PackageIdentity packageIdentity, + NuGetFramework targetFramework, + ImmutableArray repositories, + SourceCacheContext cacheContext, + ILogger logger, + ImmutableDictionary.Builder dependencies, + CancellationToken cancellationToken) + { + if (dependencies.ContainsKey(packageIdentity)) + { + return; + } + + foreach (var sourceRepository in repositories) + { + var dependencyInfoResource = await sourceRepository.GetResourceAsync(cancellationToken); + var dependencyInfo = await dependencyInfoResource.ResolvePackage( + packageIdentity, + targetFramework, +#if !NET452 + cacheContext, +#endif + logger, + cancellationToken); + if (dependencyInfo is null) + { + continue; + } + + dependencyInfo = new SourcePackageDependencyInfo(new NuGet.Packaging.Core.PackageIdentity(dependencyInfo.Id, dependencyInfo.Version), FilterDependencies(dependencyInfo.Dependencies), dependencyInfo.Listed, dependencyInfo.Source, dependencyInfo.DownloadUri, dependencyInfo.PackageHash); + dependencies.Add(packageIdentity, dependencyInfo); + foreach (var dependency in dependencyInfo.Dependencies) + { + await GetPackageDependenciesAsync(new NuGet.Packaging.Core.PackageIdentity(dependency.Id, dependency.VersionRange.MinVersion), targetFramework, repositories, cacheContext, logger, dependencies, cancellationToken); + } + + break; + } + + static IEnumerable FilterDependencies(IEnumerable dependencies) + { + return dependencies.Where(dependency => !dependency.Exclude.Contains("Compile")); + } + } + + public static class NetFramework + { + public static class Net20 + { + public static ReferenceAssemblies Default { get; } + = new ReferenceAssemblies( + "net20", + new PackageIdentity( + "Microsoft.NETFramework.ReferenceAssemblies.net20", + ReferenceAssembliesPackageVersion), + Path.Combine("build", ".NETFramework", "v2.0")) + .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default) + .AddAssemblies(ImmutableArray.Create("mscorlib", "System", "System.Data", "System.Xml")) + .AddLanguageSpecificAssemblies(LanguageNames.VisualBasic, ImmutableArray.Create("Microsoft.VisualBasic")); + + public static ReferenceAssemblies WindowsForms { get; } + = Default.AddAssemblies(ImmutableArray.Create("System.Drawing", "System.Windows.Forms")); + } + + public static class Net40 + { + public static ReferenceAssemblies Default { get; } + = new ReferenceAssemblies( + "net40", + new PackageIdentity( + "Microsoft.NETFramework.ReferenceAssemblies.net40", + ReferenceAssembliesPackageVersion), + Path.Combine("build", ".NETFramework", "v4.0")) + .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default) + .AddAssemblies(ImmutableArray.Create("mscorlib", "System", "System.Core", "System.Data", "System.Data.DataSetExtensions", "System.Xml", "System.Xml.Linq")) + .AddLanguageSpecificAssemblies(LanguageNames.CSharp, ImmutableArray.Create("Microsoft.CSharp")) + .AddLanguageSpecificAssemblies(LanguageNames.VisualBasic, ImmutableArray.Create("Microsoft.VisualBasic")); + + public static ReferenceAssemblies WindowsForms { get; } + = Default.AddAssemblies(ImmutableArray.Create("System.Deployment", "System.Drawing", "System.Windows.Forms")); + + public static ReferenceAssemblies Wpf { get; } + = Default.AddAssemblies(ImmutableArray.Create("PresentationCore", "PresentationFramework", "System.Xaml", "WindowsBase")); + } + + public static class Net45 + { + public static ReferenceAssemblies Default { get; } + = new ReferenceAssemblies( + "net45", + new PackageIdentity( + "Microsoft.NETFramework.ReferenceAssemblies.net45", + ReferenceAssembliesPackageVersion), + Path.Combine("build", ".NETFramework", "v4.5")) + .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default) + .AddAssemblies(ImmutableArray.Create("mscorlib", "System", "System.Core", "System.Data", "System.Data.DataSetExtensions", "System.Net.Http", "System.Xml", "System.Xml.Linq")) + .AddLanguageSpecificAssemblies(LanguageNames.CSharp, ImmutableArray.Create("Microsoft.CSharp")) + .AddLanguageSpecificAssemblies(LanguageNames.VisualBasic, ImmutableArray.Create("Microsoft.VisualBasic")); + + public static ReferenceAssemblies WindowsForms { get; } + = Default.AddAssemblies(ImmutableArray.Create("System.Deployment", "System.Drawing", "System.Windows.Forms")); + + public static ReferenceAssemblies Wpf { get; } + = Default.AddAssemblies(ImmutableArray.Create("PresentationCore", "PresentationFramework", "System.Xaml", "WindowsBase")); + } + + public static class Net451 + { + public static ReferenceAssemblies Default { get; } + = new ReferenceAssemblies( + "net451", + new PackageIdentity( + "Microsoft.NETFramework.ReferenceAssemblies.net451", + ReferenceAssembliesPackageVersion), + Path.Combine("build", ".NETFramework", "v4.5.1")) + .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default) + .AddAssemblies(ImmutableArray.Create("mscorlib", "System", "System.Core", "System.Data", "System.Data.DataSetExtensions", "System.Net.Http", "System.Xml", "System.Xml.Linq")) + .AddLanguageSpecificAssemblies(LanguageNames.CSharp, ImmutableArray.Create("Microsoft.CSharp")) + .AddLanguageSpecificAssemblies(LanguageNames.VisualBasic, ImmutableArray.Create("Microsoft.VisualBasic")); + + public static ReferenceAssemblies WindowsForms { get; } + = Default.AddAssemblies(ImmutableArray.Create("System.Deployment", "System.Drawing", "System.Windows.Forms")); + + public static ReferenceAssemblies Wpf { get; } + = Default.AddAssemblies(ImmutableArray.Create("PresentationCore", "PresentationFramework", "System.Xaml", "WindowsBase")); + } + + public static class Net452 + { + public static ReferenceAssemblies Default { get; } + = new ReferenceAssemblies( + "net452", + new PackageIdentity( + "Microsoft.NETFramework.ReferenceAssemblies.net452", + ReferenceAssembliesPackageVersion), + Path.Combine("build", ".NETFramework", "v4.5.2")) + .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default) + .AddAssemblies(ImmutableArray.Create("mscorlib", "System", "System.Core", "System.Data", "System.Data.DataSetExtensions", "System.Net.Http", "System.Xml", "System.Xml.Linq")) + .AddLanguageSpecificAssemblies(LanguageNames.CSharp, ImmutableArray.Create("Microsoft.CSharp")) + .AddLanguageSpecificAssemblies(LanguageNames.VisualBasic, ImmutableArray.Create("Microsoft.VisualBasic")); + + public static ReferenceAssemblies WindowsForms { get; } + = Default.AddAssemblies(ImmutableArray.Create("System.Deployment", "System.Drawing", "System.Windows.Forms")); + + public static ReferenceAssemblies Wpf { get; } + = Default.AddAssemblies(ImmutableArray.Create("PresentationCore", "PresentationFramework", "System.Xaml", "WindowsBase")); + } + + public static class Net46 + { + public static ReferenceAssemblies Default { get; } + = new ReferenceAssemblies( + "net46", + new PackageIdentity( + "Microsoft.NETFramework.ReferenceAssemblies.net46", + ReferenceAssembliesPackageVersion), + Path.Combine("build", ".NETFramework", "v4.6")) + .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default) + .AddAssemblies(ImmutableArray.Create("mscorlib", "System", "System.Core", "System.Data", "System.Data.DataSetExtensions", "System.Net.Http", "System.Xml", "System.Xml.Linq")) + .AddLanguageSpecificAssemblies(LanguageNames.CSharp, ImmutableArray.Create("Microsoft.CSharp")) + .AddLanguageSpecificAssemblies(LanguageNames.VisualBasic, ImmutableArray.Create("Microsoft.VisualBasic")); + + public static ReferenceAssemblies WindowsForms { get; } + = Default.AddAssemblies(ImmutableArray.Create("System.Deployment", "System.Drawing", "System.Windows.Forms")); + + public static ReferenceAssemblies Wpf { get; } + = Default.AddAssemblies(ImmutableArray.Create("PresentationCore", "PresentationFramework", "System.Xaml", "WindowsBase")); + } + + public static class Net461 + { + public static ReferenceAssemblies Default { get; } + = new ReferenceAssemblies( + "net461", + new PackageIdentity( + "Microsoft.NETFramework.ReferenceAssemblies.net461", + ReferenceAssembliesPackageVersion), + Path.Combine("build", ".NETFramework", "v4.6.1")) + .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default) + .AddAssemblies(ImmutableArray.Create("mscorlib", "System", "System.Core", "System.Data", "System.Data.DataSetExtensions", "System.Net.Http", "System.Xml", "System.Xml.Linq")) + .AddLanguageSpecificAssemblies(LanguageNames.CSharp, ImmutableArray.Create("Microsoft.CSharp")) + .AddLanguageSpecificAssemblies(LanguageNames.VisualBasic, ImmutableArray.Create("Microsoft.VisualBasic")); + + public static ReferenceAssemblies WindowsForms { get; } + = Default.AddAssemblies(ImmutableArray.Create("System.Deployment", "System.Drawing", "System.Windows.Forms")); + + public static ReferenceAssemblies Wpf { get; } + = Default.AddAssemblies(ImmutableArray.Create("PresentationCore", "PresentationFramework", "System.Xaml", "WindowsBase")); + } + + public static class Net462 + { + public static ReferenceAssemblies Default { get; } + = new ReferenceAssemblies( + "net462", + new PackageIdentity( + "Microsoft.NETFramework.ReferenceAssemblies.net462", + ReferenceAssembliesPackageVersion), + Path.Combine("build", ".NETFramework", "v4.6.2")) + .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default) + .AddAssemblies(ImmutableArray.Create("mscorlib", "System", "System.Core", "System.Data", "System.Data.DataSetExtensions", "System.Net.Http", "System.Xml", "System.Xml.Linq")) + .AddLanguageSpecificAssemblies(LanguageNames.CSharp, ImmutableArray.Create("Microsoft.CSharp")) + .AddLanguageSpecificAssemblies(LanguageNames.VisualBasic, ImmutableArray.Create("Microsoft.VisualBasic")); + + public static ReferenceAssemblies WindowsForms { get; } + = Default.AddAssemblies(ImmutableArray.Create("System.Deployment", "System.Drawing", "System.Windows.Forms")); + + public static ReferenceAssemblies Wpf { get; } + = Default.AddAssemblies(ImmutableArray.Create("PresentationCore", "PresentationFramework", "System.Xaml", "WindowsBase")); + } + + public static class Net47 + { + public static ReferenceAssemblies Default { get; } + = new ReferenceAssemblies( + "net47", + new PackageIdentity( + "Microsoft.NETFramework.ReferenceAssemblies.net47", + ReferenceAssembliesPackageVersion), + Path.Combine("build", ".NETFramework", "v4.7")) + .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default) + .AddAssemblies(ImmutableArray.Create("mscorlib", "System", "System.Core", "System.Data", "System.Data.DataSetExtensions", "System.Net.Http", "System.Xml", "System.Xml.Linq")) + .AddLanguageSpecificAssemblies(LanguageNames.CSharp, ImmutableArray.Create("Microsoft.CSharp")) + .AddLanguageSpecificAssemblies(LanguageNames.VisualBasic, ImmutableArray.Create("Microsoft.VisualBasic")); + + public static ReferenceAssemblies WindowsForms { get; } + = Default.AddAssemblies(ImmutableArray.Create("System.Deployment", "System.Drawing", "System.Windows.Forms")); + + public static ReferenceAssemblies Wpf { get; } + = Default.AddAssemblies(ImmutableArray.Create("PresentationCore", "PresentationFramework", "System.Xaml", "WindowsBase")); + } + + public static class Net471 + { + public static ReferenceAssemblies Default { get; } + = new ReferenceAssemblies( + "net471", + new PackageIdentity( + "Microsoft.NETFramework.ReferenceAssemblies.net471", + ReferenceAssembliesPackageVersion), + Path.Combine("build", ".NETFramework", "v4.7.1")) + .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default) + .AddAssemblies(ImmutableArray.Create("mscorlib", "System", "System.Core", "System.Data", "System.Data.DataSetExtensions", "System.Net.Http", "System.Xml", "System.Xml.Linq")) + .AddLanguageSpecificAssemblies(LanguageNames.CSharp, ImmutableArray.Create("Microsoft.CSharp")) + .AddLanguageSpecificAssemblies(LanguageNames.VisualBasic, ImmutableArray.Create("Microsoft.VisualBasic")); + + public static ReferenceAssemblies WindowsForms { get; } + = Default.AddAssemblies(ImmutableArray.Create("System.Deployment", "System.Drawing", "System.Windows.Forms")); + + public static ReferenceAssemblies Wpf { get; } + = Default.AddAssemblies(ImmutableArray.Create("PresentationCore", "PresentationFramework", "System.Xaml", "WindowsBase")); + } + + public static class Net472 + { + public static ReferenceAssemblies Default { get; } + = new ReferenceAssemblies( + "net472", + new PackageIdentity( + "Microsoft.NETFramework.ReferenceAssemblies.net472", + ReferenceAssembliesPackageVersion), + Path.Combine("build", ".NETFramework", "v4.7.2")) + .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default) + .AddAssemblies(ImmutableArray.Create("mscorlib", "System", "System.Core", "System.Data", "System.Data.DataSetExtensions", "System.Net.Http", "System.Xml", "System.Xml.Linq")) + .AddLanguageSpecificAssemblies(LanguageNames.CSharp, ImmutableArray.Create("Microsoft.CSharp")) + .AddLanguageSpecificAssemblies(LanguageNames.VisualBasic, ImmutableArray.Create("Microsoft.VisualBasic")); + + public static ReferenceAssemblies WindowsForms { get; } + = Default.AddAssemblies(ImmutableArray.Create("System.Deployment", "System.Drawing", "System.Windows.Forms")); + + public static ReferenceAssemblies Wpf { get; } + = Default.AddAssemblies(ImmutableArray.Create("PresentationCore", "PresentationFramework", "System.Xaml", "WindowsBase")); + } + + public static class Net48 + { + public static ReferenceAssemblies Default { get; } + = new ReferenceAssemblies( + "net48", + new PackageIdentity( + "Microsoft.NETFramework.ReferenceAssemblies.net48", + ReferenceAssembliesPackageVersion), + Path.Combine("build", ".NETFramework", "v4.8")) + .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default) + .AddAssemblies(ImmutableArray.Create("mscorlib", "System", "System.Core", "System.Data", "System.Data.DataSetExtensions", "System.Net.Http", "System.Xml", "System.Xml.Linq")) + .AddLanguageSpecificAssemblies(LanguageNames.CSharp, ImmutableArray.Create("Microsoft.CSharp")) + .AddLanguageSpecificAssemblies(LanguageNames.VisualBasic, ImmutableArray.Create("Microsoft.VisualBasic")); + + public static ReferenceAssemblies WindowsForms { get; } + = Default.AddAssemblies(ImmutableArray.Create("System.Deployment", "System.Drawing", "System.Windows.Forms")); + + public static ReferenceAssemblies Wpf { get; } + = Default.AddAssemblies(ImmutableArray.Create("PresentationCore", "PresentationFramework", "System.Xaml", "WindowsBase")); + } + } + + public static class NetCore + { + public static ReferenceAssemblies NetCoreApp10 { get; } + = new ReferenceAssemblies("netcoreapp1.0") + .AddPackages(ImmutableArray.Create(new PackageIdentity("Microsoft.NETCore.App", "1.0.16"))); + + public static ReferenceAssemblies NetCoreApp11 { get; } + = new ReferenceAssemblies("netcoreapp1.1") + .AddPackages(ImmutableArray.Create(new PackageIdentity("Microsoft.NETCore.App", "1.1.13"))); + + public static ReferenceAssemblies NetCoreApp20 { get; } + = new ReferenceAssemblies("netcoreapp2.0") + .AddPackages(ImmutableArray.Create(new PackageIdentity("Microsoft.NETCore.App", "2.0.9"))); + + public static ReferenceAssemblies NetCoreApp21 { get; } + = new ReferenceAssemblies("netcoreapp2.1") + .AddPackages(ImmutableArray.Create(new PackageIdentity("Microsoft.NETCore.App", "2.1.13"))); + + public static ReferenceAssemblies NetCoreApp30 { get; } + = new ReferenceAssemblies( + "netcoreapp3.0", + new PackageIdentity( + "Microsoft.NETCore.App.Ref", + "3.0.0"), + Path.Combine("ref", "netcoreapp3.0")); + + public static ReferenceAssemblies NetCoreApp31 { get; } + = new ReferenceAssemblies( + "netcoreapp3.1", + new PackageIdentity( + "Microsoft.NETCore.App.Ref", + "3.1.0"), + Path.Combine("ref", "netcoreapp3.1")); + } + + public static class Net + { + private static readonly Lazy _lazyNet50 = + new Lazy(() => + { + if (!NuGetFramework.Parse("net5.0").IsPackageBased) + { + // The NuGet version provided at runtime does not recognize the 'net5.0' target framework + throw new NotSupportedException("The 'net5.0' target framework is not supported by this version of NuGet."); + } + + return new ReferenceAssemblies( + "net5.0", + new PackageIdentity( + "Microsoft.NETCore.App.Ref", + "5.0.0"), + Path.Combine("ref", "net5.0")); + }); + + private static readonly Lazy _lazyNet60 = + new Lazy(() => + { + if (!NuGetFramework.Parse("net6.0").IsPackageBased) + { + // The NuGet version provided at runtime does not recognize the 'net6.0' target framework + throw new NotSupportedException("The 'net6.0' target framework is not supported by this version of NuGet."); + } + + return new ReferenceAssemblies( + "net6.0", + new PackageIdentity( + "Microsoft.NETCore.App.Ref", + "6.0.0-preview.6.21352.12"), + Path.Combine("ref", "net6.0")); + }); + + public static ReferenceAssemblies Net50 => _lazyNet50.Value; + + public static ReferenceAssemblies Net60 => _lazyNet60.Value; + } + + public static class NetStandard + { + public static ReferenceAssemblies NetStandard10 { get; } + = new ReferenceAssemblies("netstandard1.0") + .AddPackages(ImmutableArray.Create(new PackageIdentity("NETStandard.Library", "1.6.1"))); + + public static ReferenceAssemblies NetStandard11 { get; } + = new ReferenceAssemblies("netstandard1.1") + .AddPackages(ImmutableArray.Create(new PackageIdentity("NETStandard.Library", "1.6.1"))); + + public static ReferenceAssemblies NetStandard12 { get; } + = new ReferenceAssemblies("netstandard1.2") + .AddPackages(ImmutableArray.Create(new PackageIdentity("NETStandard.Library", "1.6.1"))); + + public static ReferenceAssemblies NetStandard13 { get; } + = new ReferenceAssemblies("netstandard1.3") + .AddPackages(ImmutableArray.Create(new PackageIdentity("NETStandard.Library", "1.6.1"))); + + public static ReferenceAssemblies NetStandard14 { get; } + = new ReferenceAssemblies("netstandard1.4") + .AddPackages(ImmutableArray.Create(new PackageIdentity("NETStandard.Library", "1.6.1"))); + + public static ReferenceAssemblies NetStandard15 { get; } + = new ReferenceAssemblies("netstandard1.5") + .AddPackages(ImmutableArray.Create(new PackageIdentity("NETStandard.Library", "1.6.1"))); + + public static ReferenceAssemblies NetStandard16 { get; } + = new ReferenceAssemblies("netstandard1.6") + .AddPackages(ImmutableArray.Create(new PackageIdentity("NETStandard.Library", "1.6.1"))); + + public static ReferenceAssemblies NetStandard20 { get; } + = new ReferenceAssemblies( + "netstandard2.0", + new PackageIdentity( + "NETStandard.Library", + "2.0.3"), + Path.Combine("build", "netstandard2.0", "ref")) + .AddAssemblies(ImmutableArray.Create("netstandard")) + .AddFacadeAssemblies(ImmutableArray.Create( + "Microsoft.Win32.Primitives", + "System.AppContext", + "System.Collections.Concurrent", + "System.Collections", + "System.Collections.NonGeneric", + "System.Collections.Specialized", + "System.ComponentModel", + "System.ComponentModel.EventBasedAsync", + "System.ComponentModel.Primitives", + "System.ComponentModel.TypeConverter", + "System.Console", + "System.Data.Common", + "System.Diagnostics.Contracts", + "System.Diagnostics.Debug", + "System.Diagnostics.FileVersionInfo", + "System.Diagnostics.Process", + "System.Diagnostics.StackTrace", + "System.Diagnostics.TextWriterTraceListener", + "System.Diagnostics.Tools", + "System.Diagnostics.TraceSource", + "System.Diagnostics.Tracing", + "System.Drawing.Primitives", + "System.Dynamic.Runtime", + "System.Globalization.Calendars", + "System.Globalization", + "System.Globalization.Extensions", + "System.IO.Compression", + "System.IO.Compression.ZipFile", + "System.IO", + "System.IO.FileSystem", + "System.IO.FileSystem.DriveInfo", + "System.IO.FileSystem.Primitives", + "System.IO.FileSystem.Watcher", + "System.IO.IsolatedStorage", + "System.IO.MemoryMappedFiles", + "System.IO.Pipes", + "System.IO.UnmanagedMemoryStream", + "System.Linq", + "System.Linq.Expressions", + "System.Linq.Parallel", + "System.Linq.Queryable", + "System.Net.Http", + "System.Net.NameResolution", + "System.Net.NetworkInformation", + "System.Net.Ping", + "System.Net.Primitives", + "System.Net.Requests", + "System.Net.Security", + "System.Net.Sockets", + "System.Net.WebHeaderCollection", + "System.Net.WebSockets.Client", + "System.Net.WebSockets", + "System.ObjectModel", + "System.Reflection", + "System.Reflection.Extensions", + "System.Reflection.Primitives", + "System.Resources.Reader", + "System.Resources.ResourceManager", + "System.Resources.Writer", + "System.Runtime.CompilerServices.VisualC", + "System.Runtime", + "System.Runtime.Extensions", + "System.Runtime.Handles", + "System.Runtime.InteropServices", + "System.Runtime.InteropServices.RuntimeInformation", + "System.Runtime.Numerics", + "System.Runtime.Serialization.Formatters", + "System.Runtime.Serialization.Json", + "System.Runtime.Serialization.Primitives", + "System.Runtime.Serialization.Xml", + "System.Security.Claims", + "System.Security.Cryptography.Algorithms", + "System.Security.Cryptography.Csp", + "System.Security.Cryptography.Encoding", + "System.Security.Cryptography.Primitives", + "System.Security.Cryptography.X509Certificates", + "System.Security.Principal", + "System.Security.SecureString", + "System.Text.Encoding", + "System.Text.Encoding.Extensions", + "System.Text.RegularExpressions", + "System.Threading", + "System.Threading.Overlapped", + "System.Threading.Tasks", + "System.Threading.Tasks.Parallel", + "System.Threading.Thread", + "System.Threading.ThreadPool", + "System.Threading.Timer", + "System.ValueTuple", + "System.Xml.ReaderWriter", + "System.Xml.XDocument", + "System.Xml.XmlDocument", + "System.Xml.XmlSerializer", + "System.Xml.XPath", + "System.Xml.XPath.XDocument", + "mscorlib", + "System.ComponentModel.Composition", + "System.Core", + "System", + "System.Data", + "System.Drawing", + "System.IO.Compression.FileSystem", + "System.Net", + "System.Numerics", + "System.Runtime.Serialization", + "System.ServiceModel.Web", + "System.Transactions", + "System.Web", + "System.Windows", + "System.Xml", + "System.Xml.Linq", + "System.Xml.Serialization")); + + public static ReferenceAssemblies NetStandard21 { get; } + = new ReferenceAssemblies( + "netstandard2.1", + new PackageIdentity( + "NETStandard.Library.Ref", + "2.1.0"), + Path.Combine("ref", "netstandard2.1")); + } + + internal static class TestAccessor + { + public static bool IsPackageBased(string targetFramework) + { + var framework = NuGetFramework.ParseFolder(targetFramework); + return framework.IsPackageBased; + } + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/RoslynDebug.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/RoslynDebug.cs new file mode 100644 index 000000000..96e621539 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/RoslynDebug.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.CodeAnalysis.Testing +{ + internal static class RoslynDebug + { + /// + [Conditional("DEBUG")] + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1405:Debug.Assert should provide message text", Justification = "This is a wrapper for Debug.Assert.")] + public static void Assert([DoesNotReturnIf(false)] bool b) + => Debug.Assert(b); + + /// + [Conditional("DEBUG")] + public static void Assert([DoesNotReturnIf(false)] bool b, string message) + => Debug.Assert(b, message); + + [Conditional("DEBUG")] + public static void AssertNotNull([NotNull] T value) + where T : class? + { + Assert(value is object, "Unexpected null reference"); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/SolutionState.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/SolutionState.cs new file mode 100644 index 000000000..0e248099a --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/SolutionState.cs @@ -0,0 +1,517 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Testing +{ + public class SolutionState : ProjectState + { + public SolutionState(string name, string language, string defaultPrefix, string defaultExtension) + : base(name, language, defaultPrefix, defaultExtension) + { + AdditionalProjects = new ProjectCollection(language, defaultExtension); + } + + /// + /// Gets or sets a value indicating the manner in which properties are inherited from base test states. When + /// this property is not set to a specific value, the default varies according to the type of test state: + /// + /// For original (input) sources, the default value is . + /// For fixed (output) sources, the default value is . + /// For uncorrected (output) sources, the default value is . + /// + /// + public StateInheritanceMode? InheritanceMode { get; set; } + + /// + /// Gets a collection of additional projects to include in the solution. + /// + public ProjectCollection AdditionalProjects { get; } + + /// + /// Gets the list of diagnostics expected in the source(s) and/or additonal files. + /// + public List ExpectedDiagnostics { get; } = new List(); + + /// + /// Gets or sets a value indicating the manner in which markup syntax is treated within test inputs and outputs. + /// When this property is not set to a specific value, the default varies according to the type of test state: + /// + /// For original (input) sources, the default value is . + /// For fixed (output) sources, the default value is . + /// For uncorrected (output) sources, the default value is . + /// + /// + /// + /// Diagnostics expressed using markup are combined with explicitly-specified expected diagnostics. + /// + /// Supported markup syntax includes the following: + /// + /// + /// [|text|]: indicates that a diagnostic is reported for text. The diagnostic + /// descriptor is located via . This syntax may only + /// be used when the first analyzer provided by + /// supports a single diagnostic. + /// {|ID1:text|}: indicates that a diagnostic with ID ID1 is reported for + /// text. The diagnostic descriptor for ID1 is located via + /// . If no matching descriptor is found, the + /// diagnostic is assumed to be a compiler-reported diagnostic with the specified ID and severity + /// . + /// + /// + public MarkupMode? MarkupHandling { get; set; } + + /// + /// Applies the using a specified base state. + /// + /// + /// This method evaluates , and places the resulting + /// additional files in the collection of the result before + /// returning. + /// + /// The base state to inherit from, or if the current state is + /// the root state. + /// The set of diagnostic IDs to treat as fixable. Fixable diagnostics present + /// in the collection of the base state are only inherited for + /// . + /// A new representing the current state with inherited values applied + /// where appropriate. The of the result is + /// . + public SolutionState WithInheritedValuesApplied(SolutionState? baseState, ImmutableArray fixableDiagnostics) + { + var inheritanceMode = InheritanceMode; + var markupHandling = MarkupHandling; + if (inheritanceMode == null || markupHandling == null) + { + if (baseState == null) + { + inheritanceMode = inheritanceMode ?? StateInheritanceMode.Explicit; + markupHandling = markupHandling ?? MarkupMode.Allow; + } + else if (HasAnyContentChanges(willInherit: inheritanceMode != StateInheritanceMode.Explicit, this, baseState)) + { + inheritanceMode = inheritanceMode ?? StateInheritanceMode.AutoInherit; + markupHandling = markupHandling ?? MarkupMode.IgnoreFixable; + } + else + { + inheritanceMode = inheritanceMode ?? StateInheritanceMode.AutoInheritAll; + markupHandling = markupHandling ?? baseState.MarkupHandling ?? MarkupMode.Allow; + } + } + + if (inheritanceMode != StateInheritanceMode.AutoInherit + && inheritanceMode != StateInheritanceMode.Explicit + && inheritanceMode != StateInheritanceMode.AutoInheritAll) + { + throw new InvalidOperationException($"Unexpected inheritance mode: {inheritanceMode}"); + } + + if (baseState?.AdditionalFilesFactories.Count > 0) + { + throw new InvalidOperationException("The base state should already have its inheritance state evaluated prior to its use as a base state."); + } + + var result = new SolutionState(Name, Language, DefaultPrefix, DefaultExtension); + + result.ReferenceAssemblies = ReferenceAssemblies; + result.OutputKind = OutputKind; + result.DocumentationMode = DocumentationMode; + + if (inheritanceMode != StateInheritanceMode.Explicit && baseState != null) + { + result.ReferenceAssemblies ??= baseState.ReferenceAssemblies; + result.OutputKind ??= baseState.OutputKind; + result.DocumentationMode ??= baseState.DocumentationMode; + + if (Sources.Count == 0) + { + result.Sources.AddRange(baseState.Sources); + } + + if (GeneratedSources.Count == 0) + { + result.GeneratedSources.AddRange(baseState.GeneratedSources); + } + + if (AdditionalFiles.Count == 0) + { + result.AdditionalFiles.AddRange(baseState.AdditionalFiles); + } + + if (AnalyzerConfigFiles.Count == 0) + { + result.AnalyzerConfigFiles.AddRange(baseState.AnalyzerConfigFiles); + } + + if (AdditionalProjects.Count == 0) + { + result.AdditionalProjects.AddRange(baseState.AdditionalProjects); + } + + if (AdditionalProjectReferences.Count == 0) + { + result.AdditionalProjectReferences.AddRange(baseState.AdditionalProjectReferences); + } + + if (AdditionalReferences.Count == 0) + { + result.AdditionalReferences.AddRange(baseState.AdditionalReferences); + } + + if (ExpectedDiagnostics.Count == 0) + { + if (inheritanceMode == StateInheritanceMode.AutoInherit) + { + result.ExpectedDiagnostics.AddRange(baseState.ExpectedDiagnostics.Where(diagnostic => !fixableDiagnostics.Contains(diagnostic.Id))); + } + else + { + result.ExpectedDiagnostics.AddRange(baseState.ExpectedDiagnostics); + } + } + } + + result.MarkupHandling = markupHandling; + result.InheritanceMode = StateInheritanceMode.Explicit; + result.Sources.AddRange(Sources); + result.GeneratedSources.AddRange(GeneratedSources); + result.AdditionalFiles.AddRange(AdditionalFiles); + result.AnalyzerConfigFiles.AddRange(AnalyzerConfigFiles); + result.AdditionalProjects.AddRange(AdditionalProjects); + result.AdditionalProjectReferences.AddRange(AdditionalProjectReferences); + result.AdditionalReferences.AddRange(AdditionalReferences); + result.ExpectedDiagnostics.AddRange(ExpectedDiagnostics); + result.AdditionalFiles.AddRange(AdditionalFilesFactories.SelectMany(factory => factory())); + return result; + } + + private static bool HasAnyContentChanges(bool willInherit, SolutionState state, SolutionState baseState) + { + if (state == null) + { + throw new ArgumentNullException(nameof(state)); + } + + if (baseState == null) + { + throw new ArgumentNullException(nameof(baseState)); + } + + if ((!willInherit || state.Sources.Any()) && !ContentEqual(state.Sources, baseState.Sources)) + { + return true; + } + + if ((!willInherit || state.GeneratedSources.Any()) && !ContentEqual(state.GeneratedSources, baseState.GeneratedSources)) + { + return true; + } + + if ((!willInherit || state.AdditionalFiles.Any()) && !ContentEqual(state.AdditionalFiles, baseState.AdditionalFiles)) + { + return true; + } + + if ((!willInherit || state.AnalyzerConfigFiles.Any()) && !ContentEqual(state.AnalyzerConfigFiles, baseState.AnalyzerConfigFiles)) + { + return true; + } + + if ((!willInherit || state.AdditionalReferences.Any()) && !state.AdditionalReferences.SequenceEqual(baseState.AdditionalReferences)) + { + return true; + } + + return false; + } + + private static bool ContentEqual(SourceFileCollection x, SourceFileCollection y) + { + if (x.Count != y.Count) + { + return false; + } + + for (var i = 0; i < x.Count; i++) + { + if (x[i].filename != y[i].filename) + { + return false; + } + + if (!Equals(x[i].content.Encoding, y[i].content.Encoding)) + { + return false; + } + + if (!x[i].content.ContentEquals(y[i].content)) + { + return false; + } + } + + return true; + } + + /// + /// Processes the markup syntax for this according to the current + /// , and returns a new with the + /// , , + /// , , and + /// updated accordingly. + /// + /// Additional options to apply during markup processing. + /// The diagnostic descriptor to use for markup spans without an explicit name, + /// or if no such default exists. + /// The diagnostics supported by analyzers used by the test. + /// The set of diagnostic IDs to treat as fixable. This value is only used when + /// is . + /// The default file path for diagnostics reported in source code. + /// A new with all markup processing completed according to the current + /// . The of the returned instance is + /// . + /// If is not + /// . + public SolutionState WithProcessedMarkup(MarkupOptions markupOptions, DiagnosticDescriptor? defaultDiagnostic, ImmutableArray supportedDiagnostics, ImmutableArray fixableDiagnostics, string defaultPath) + { + if (InheritanceMode != StateInheritanceMode.Explicit) + { + throw new InvalidOperationException("Inheritance processing must complete before markup processing."); + } + + var markupLocations = ImmutableDictionary.Empty; + (var expected, var testSources) = ProcessMarkupSources(Sources, ExpectedDiagnostics, ref markupLocations, markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, defaultPath); + var (additionalExpected2, testGeneratedSources) = ProcessMarkupSources(GeneratedSources, expected, ref markupLocations, markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, defaultPath); + var (additionalExpected1, additionalFiles) = ProcessMarkupSources(AdditionalFiles.Concat(AdditionalFilesFactories.SelectMany(factory => factory())), additionalExpected2, ref markupLocations, markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, defaultPath); + var (additionalExpected, analyzerConfigFiles) = ProcessMarkupSources(AnalyzerConfigFiles, additionalExpected1, ref markupLocations, markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, defaultPath); + + var result = new SolutionState(Name, Language, DefaultPrefix, DefaultExtension); + result.MarkupHandling = MarkupMode.None; + result.InheritanceMode = StateInheritanceMode.Explicit; + result.ReferenceAssemblies = ReferenceAssemblies; + result.OutputKind = OutputKind; + result.DocumentationMode = DocumentationMode; + result.Sources.AddRange(testSources); + result.GeneratedSources.AddRange(testGeneratedSources); + result.AdditionalFiles.AddRange(additionalFiles); + result.AnalyzerConfigFiles.AddRange(analyzerConfigFiles); + + foreach (var (projectName, projectState) in AdditionalProjects) + { + var (correctedIntermediateDiagnostics, additionalProjectSources) = ProcessMarkupSources(projectState.Sources, additionalExpected, ref markupLocations, markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, defaultPath); + var (correctedDiagnostics2, additionalProjectGeneratedSources) = ProcessMarkupSources(projectState.GeneratedSources, correctedIntermediateDiagnostics, ref markupLocations, markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, defaultPath); + var (correctedDiagnostics1, additionalProjectAdditionalFiles) = ProcessMarkupSources(projectState.AdditionalFiles.Concat(projectState.AdditionalFilesFactories.SelectMany(factory => factory())), correctedDiagnostics2, ref markupLocations, markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, defaultPath); + var (correctedDiagnostics, additionalProjectAnalyzerConfigFiles) = ProcessMarkupSources(projectState.AnalyzerConfigFiles, correctedDiagnostics1, ref markupLocations, markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, defaultPath); + + var processedProjectState = new ProjectState(projectState); + processedProjectState.Sources.Clear(); + processedProjectState.Sources.AddRange(additionalProjectSources); + processedProjectState.GeneratedSources.Clear(); + processedProjectState.GeneratedSources.AddRange(additionalProjectGeneratedSources); + processedProjectState.AdditionalFiles.Clear(); + processedProjectState.AdditionalFilesFactories.Clear(); + processedProjectState.AdditionalFiles.AddRange(additionalProjectAdditionalFiles); + processedProjectState.AnalyzerConfigFiles.Clear(); + processedProjectState.AnalyzerConfigFiles.AddRange(additionalProjectAnalyzerConfigFiles); + + result.AdditionalProjects.Add(projectName, processedProjectState); + additionalExpected = correctedDiagnostics; + } + + for (var i = 0; i < additionalExpected.Length; i++) + { + additionalExpected[i] = additionalExpected[i].WithAppliedMarkupLocations(markupLocations); + } + + result.AdditionalProjectReferences.AddRange(AdditionalProjectReferences); + result.AdditionalReferences.AddRange(AdditionalReferences); + result.ExpectedDiagnostics.AddRange(additionalExpected); + return result; + } + + private (DiagnosticResult[] expectedDiagnostics, (string filename, SourceText content)[] sources) ProcessMarkupSources( + IEnumerable<(string filename, SourceText content)> sources, + IEnumerable explicitDiagnostics, + ref ImmutableDictionary markupLocations, + MarkupOptions markupOptions, + DiagnosticDescriptor? defaultDiagnostic, + ImmutableArray supportedDiagnostics, + ImmutableArray fixableDiagnostics, + string defaultPath) + { + if (MarkupHandling is null) + { + throw new InvalidOperationException(); + } + + if (MarkupHandling == MarkupMode.None) + { + return (explicitDiagnostics.Select(diagnostic => diagnostic.WithDefaultPath(defaultPath)).ToArray(), sources.ToArray()); + } + + var sourceFiles = new List<(string filename, SourceText content)>(); + var diagnostics = new List(explicitDiagnostics.Select(diagnostic => diagnostic.WithDefaultPath(defaultPath))); + foreach ((var filename, var content) in sources) + { + TestFileMarkupParser.GetPositionsAndSpans(content.ToString(), out var output, out var positions, out var namedSpans); + sourceFiles.Add((filename, content.Replace(new TextSpan(0, content.Length), output))); + if (positions.IsEmpty && namedSpans.IsEmpty) + { + // No markup notation in this input + continue; + } + + if (MarkupHandling == MarkupMode.Ignore) + { + // The source contained markup, which was removed and ignored + continue; + } + + var sourceText = SourceText.From(output, content.Encoding, content.ChecksumAlgorithm); + foreach (var position in positions) + { + var diagnostic = CreateDiagnosticForPosition(markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, string.Empty, filename, sourceText, position); + if (!diagnostic.HasValue) + { + continue; + } + + diagnostics.Add(diagnostic.Value); + } + + foreach ((var name, var spans) in namedSpans.OrderBy(pair => pair.Key, StringComparer.Ordinal)) + { + if (name.StartsWith("#")) + { + // This is an indexed location. Keep track of it for later processing. + if (markupLocations.ContainsKey(name) + || spans.Length != 1) + { + throw new InvalidOperationException($"Input contains multiple markup locations with key '{name}'"); + } + + var linePositionSpan = sourceText.Lines.GetLinePositionSpan(spans[0]); + markupLocations = markupLocations.Add(name, new FileLinePositionSpan(filename, linePositionSpan)); + continue; + } + + foreach (var span in spans) + { + var diagnostic = CreateDiagnosticForSpan(markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, name, filename, sourceText, span); + if (!diagnostic.HasValue) + { + continue; + } + + diagnostics.Add(diagnostic.Value); + } + } + } + + return (diagnostics.ToArray(), sourceFiles.ToArray()); + } + + private DiagnosticResult? CreateDiagnosticForPosition( + MarkupOptions markupOptions, + DiagnosticDescriptor? defaultDiagnostic, + ImmutableArray supportedDiagnostics, + ImmutableArray fixableDiagnostics, + string diagnosticId, + string filename, + SourceText content, + int position) + { + var diagnosticResult = CreateDiagnostic(markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, diagnosticId); + if (diagnosticResult == null) + { + return null; + } + + var linePosition = content.Lines.GetLinePosition(position); + return diagnosticResult.Value.WithLocation(filename, linePosition); + } + + private DiagnosticResult? CreateDiagnosticForSpan( + MarkupOptions markupOptions, + DiagnosticDescriptor? defaultDiagnostic, + ImmutableArray supportedDiagnostics, + ImmutableArray fixableDiagnostics, + string diagnosticId, + string filename, + SourceText content, + TextSpan span) + { + var diagnosticResult = CreateDiagnostic(markupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, diagnosticId); + if (diagnosticResult == null) + { + return null; + } + + var linePositionSpan = content.Lines.GetLinePositionSpan(span); + return diagnosticResult.Value.WithSpan(new FileLinePositionSpan(filename, linePositionSpan)); + } + + private DiagnosticResult? CreateDiagnostic( + MarkupOptions markupOptions, + DiagnosticDescriptor? defaultDiagnostic, + ImmutableArray supportedDiagnostics, + ImmutableArray fixableDiagnostics, + string diagnosticId) + { + if (MarkupHandling is null) + { + throw new InvalidOperationException(); + } + + DiagnosticResult diagnosticResult; + if (string.IsNullOrEmpty(diagnosticId)) + { + if (defaultDiagnostic is null) + { + throw new InvalidOperationException($"Markup syntax can only omit the diagnostic ID if the first analyzer only supports a single diagnostic. To customize the default value, override {nameof(AnalyzerTest)}.{nameof(AnalyzerTest.GetDefaultDiagnostic)} or specify {nameof(MarkupOptions)}.{nameof(MarkupOptions.UseFirstDescriptor)}."); + } + + if (MarkupHandling == MarkupMode.IgnoreFixable && fixableDiagnostics.Contains(defaultDiagnostic.Id)) + { + return null; + } + + diagnosticResult = new DiagnosticResult(defaultDiagnostic); + } + else + { + if (MarkupHandling == MarkupMode.IgnoreFixable && fixableDiagnostics.Contains(diagnosticId)) + { + return null; + } + + var descriptors = supportedDiagnostics.Where(d => d.Id == diagnosticId); + var descriptor = descriptors.FirstOrDefault(); + if (descriptor != null) + { + if (!markupOptions.HasFlag(MarkupOptions.UseFirstDescriptor) + && descriptors.Skip(1).Any()) + { + throw new InvalidOperationException($"Multiple diagnostic descriptors with ID {diagnosticId} were found. Use the explicitly diagnostic creation syntax or specify {nameof(MarkupOptions)}.{nameof(MarkupOptions.UseFirstDescriptor)} to use the first matching diagnostic."); + } + + diagnosticResult = new DiagnosticResult(descriptor); + } + else + { + // This must be a compiler error + diagnosticResult = new DiagnosticResult(diagnosticId, DiagnosticSeverity.Error); + } + } + + return diagnosticResult.WithMessage(null).WithOptions(DiagnosticOptions.IgnoreAdditionalLocations | DiagnosticOptions.IgnoreSeverity); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/SourceFileCollection.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/SourceFileCollection.cs new file mode 100644 index 000000000..a4d69e536 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/SourceFileCollection.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Testing +{ + public class SourceFileCollection : List<(string filename, SourceText content)> + { + public void Add((string filename, string content) file) + { + Add((file.filename, SourceText.From(file.content))); + } + + public void Add((Type sourceGeneratorType, string filename, string content) file) + { + var contentWithEncoding = SourceText.From(file.content, Encoding.UTF8); + Add((file.sourceGeneratorType, file.filename, contentWithEncoding)); + } + + public void Add((Type sourceGeneratorType, string filename, SourceText content) file) + { + var generatedPath = Path.Combine(file.sourceGeneratorType.GetTypeInfo().Assembly.GetName().Name ?? string.Empty, file.sourceGeneratorType.FullName!, file.filename); + Add((generatedPath, file.content)); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/SourceFileList.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/SourceFileList.cs new file mode 100644 index 000000000..e4d12b959 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/SourceFileList.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Testing +{ + public class SourceFileList : SourceFileCollection + { + private readonly string _defaultPrefix; + private readonly string _defaultExtension; + + public SourceFileList(string defaultPrefix, string defaultExtension) + { + _defaultPrefix = defaultPrefix; + _defaultExtension = defaultExtension; + } + + public void Add(string content) + { + Add(($"{_defaultPrefix}{Count}.{_defaultExtension}", content)); + } + + public void Add(SourceText content) + { + Add(($"{_defaultPrefix}{Count}.{_defaultExtension}", content)); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/StateInheritanceMode.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/StateInheritanceMode.cs new file mode 100644 index 000000000..0966e76af --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/StateInheritanceMode.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Indicates the manner in which properties are inherited from base test states. + /// + /// + public enum StateInheritanceMode + { + /// + /// The contents of the may be explicitly specified, but unspecified elements of + /// partially-specified state instances are inherited from another source. Fixable diagnostics are not + /// inherited. + /// + AutoInherit, + + /// + /// The contents of the are fully and explicitly specified. + /// + Explicit, + + /// + /// The contents of the may be explicitly specified, but unspecified elements of + /// partially-specified state instances are inherited from another source. All diagnostics, including fixable + /// diagnostics, are inherited. + /// + AutoInheritAll, + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/TestBehaviors.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/TestBehaviors.cs new file mode 100644 index 000000000..9fad0cfd1 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/TestBehaviors.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Specifies non-standard analyzer behaviors which impact testing. + /// + [Flags] + public enum TestBehaviors + { + /// + /// No special behaviors apply. + /// + None = 0, + + /// + /// Skip the generated code exclusion check. + /// + /// + /// This flag is only used in cases where one or more analyzers does not explicitly configure generated + /// code analysis via the + /// API. + /// + /// By default, the analyzer test framework verifies that analyzer which report diagnostics do not report + /// diagnostics in generated code. While some analyzers, e.g. security analyzers, are expected to report + /// diagnostics in all code, most analyzers are expected to only report diagnostics in user-created code. + /// + SkipGeneratedCodeCheck = 0x01, + + /// + /// Skip a verification check that diagnostics will not be reported if a #pragma warning disable appears + /// at the beginning of the file. + /// + SkipSuppressionCheck = 0x02, + + /// + /// Skip a verification check that the contents of match the sources + /// produced by the active source generators (if any). + /// + /// + /// When this flag is set, the property is completely ignored; tests + /// are encouraged to leave it empty for optimal readability. + /// + SkipGeneratedSourcesCheck = 0x04, + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/TestFileMarkupParser.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/TestFileMarkupParser.cs new file mode 100644 index 000000000..b56eeabcb --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/TestFileMarkupParser.cs @@ -0,0 +1,494 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// To aid with testing, we define a special type of text file that can encode additional + /// information in it. This prevents a test writer from having to carry around multiple sources + /// of information that must be reconstituted. For example, instead of having to keep around the + /// contents of a file and and the location of the cursor, the tester can just provide a + /// string with the $$ character in it. This allows for easy creation of "FIT" tests where all + /// that needs to be provided are strings that encode every bit of state necessary in the string + /// itself. + /// + /// The current set of encoded features we support are: + /// + /// + /// + /// $$ + /// A position in the file. The number of times this is allowed to appear varies depending on the + /// specific call. + /// + /// + /// [| ... |] + /// A span of text in the file. There can be many of these and they can be nested and/or overlap + /// the $$ position. + /// + /// + /// {|Name: ... |} + /// A span of text in the file annotated with an identifier. There can be many of these, including + /// ones with the same name. + /// + /// + /// + /// Additional encoded features can be added on a case by case basis. + /// + public static class TestFileMarkupParser + { + private const string PositionString = "$$"; + private const string SpanStartString = "[|"; + private const string SpanEndString = "|]"; + private const string NamedSpanStartString = "{|"; + private const string NamedSpanEndString = "|}"; + private const string NamedSpanNumberedEndString = "|#"; + + private static readonly Regex s_namedSpanStartRegex = new Regex( + @"\{\| ([^:|[\]{}]+) \:", + RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace); + + private static readonly Regex s_namedSpanEndRegex = new Regex( + @"\| (\#\d+) \}", + RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace); + + private static void Parse(string input, out string output, out ImmutableArray positions, out ImmutableDictionary> spans) + { + Parse(input, out output, out positions, out var startPositions, out var endPositions); + if (startPositions.Length != endPositions.Length) + { + throw new ArgumentException($"The input contained '{startPositions.Length}' starting spans and '{endPositions.Length}' ending spans."); + } + + var startPositionsList = startPositions.ToImmutableList().ToBuilder(); + var endPositionsList = endPositions.ToImmutableList().ToBuilder(); + + var spansBuilder = ImmutableDictionary.CreateBuilder.Builder>(); + + // Start by matching all end positions that were provided by ID + for (var i = 0; i < endPositionsList.Count; i++) + { + var (inputPosition, outputPosition, key) = endPositionsList[i]; + if (string.IsNullOrEmpty(key)) + { + continue; + } + + if (spansBuilder.ContainsKey(key)) + { + throw new ArgumentException($"The input contained more than one ending tag for span '{key}'", nameof(input)); + } + + var index = startPositionsList.FindIndex(start => start.key == key); + if (index < 0) + { + throw new ArgumentException($"The input did not contain a start tag for span '{key}'", nameof(input)); + } + + spansBuilder[key] = ImmutableArray.Create(TextSpan.FromBounds(startPositionsList[index].outputPosition, outputPosition)).ToBuilder(); + endPositionsList.RemoveAt(i); + startPositionsList.RemoveAt(index); + i--; + } + + // Match the remaining spans using a simple stack algorithm + var startIndex = 0; + while (startPositionsList.Count > 0) + { + Debug.Assert(startPositionsList.Count == endPositionsList.Count, "Assertion failed: startPositionsList.Count == endPositionsList.Count"); + Debug.Assert(startIndex >= 0 && startIndex < startPositionsList.Count, "Assertion failed: startIndex >= 0 && startIndex < startPositionsList.Count"); + + var (startInputPosition, startOutputPosition, startKey) = startPositionsList[startIndex]; + var (endInputPosition, endOutputPosition, endKey) = endPositionsList[0]; + Debug.Assert(endKey == string.Empty, "Assertion failed: endKey == string.Empty"); + if (startInputPosition > endInputPosition) + { + if (startIndex == 0) + { + throw new ArgumentException($"Mismatched end tag found at position '{endInputPosition}'", nameof(input)); + } + + startIndex--; + (startInputPosition, startOutputPosition, startKey) = startPositionsList[startIndex]; + startPositionsList.RemoveAt(startIndex); + endPositionsList.RemoveAt(0); + if (startKey.StartsWith("#") && spansBuilder.ContainsKey(startKey)) + { + throw new ArgumentException($"The input contained more than one start tag for span '{startKey}'", nameof(input)); + } + + var textSpanBuilder = spansBuilder.GetOrAdd(startKey, _ => ImmutableArray.CreateBuilder()); + textSpanBuilder.Add(TextSpan.FromBounds(startOutputPosition, endOutputPosition)); + continue; + } + else + { + if (startIndex == startPositionsList.Count - 1) + { + startPositionsList.RemoveAt(startIndex); + endPositionsList.RemoveAt(0); + if (startKey.StartsWith("#") && spansBuilder.ContainsKey(startKey)) + { + throw new ArgumentException($"The input contained more than one start tag for span '{startKey}'", nameof(input)); + } + + var textSpanBuilder = spansBuilder.GetOrAdd(startKey, _ => ImmutableArray.CreateBuilder()); + textSpanBuilder.Add(TextSpan.FromBounds(startOutputPosition, endOutputPosition)); + + startIndex--; + continue; + } + else + { + startIndex++; + continue; + } + } + } + + spans = spansBuilder.ToImmutableDictionary(pair => pair.Key, pair => pair.Value.ToImmutable()); + } + + /// + /// Parses the input markup to find standalone positions and the start and end positions of text spans. + /// + /// The input markup. + /// The output content with markup syntax removed from . + /// A list of positions defined in markup ($$). + /// A list of starting positions of spans in markup. The key of the element is a + /// position (the location of the [| or {|). The value of the element is the text content + /// of a {|text: starting syntax, or if the [| syntax was used. This list + /// preserves the original order of starting markup tags in the input. + /// A list of ending positions of spans in markup. The key of the element is a + /// position (the location of the |] or |}). The value of the element is the #id content of + /// a |#id} ending syntax, or if the |] or |} syntax was used. This + /// list preserves the original order of the ending markup tags in the input. + private static void Parse(string input, out string output, out ImmutableArray positions, out ImmutableArray<(int inputPosition, int outputPosition, string key)> startPositions, out ImmutableArray<(int inputPosition, int outputPosition, string key)> endPositions) + { + var positionsBuilder = ImmutableArray.CreateBuilder(); + var startPositionsBuilder = ImmutableArray.CreateBuilder<(int inputPosition, int outputPosition, string key)>(); + var endPositionsBuilder = ImmutableArray.CreateBuilder<(int inputPosition, int outputPosition, string key)>(); + + var outputBuilder = new StringBuilder(); + + var currentIndexInInput = 0; + var inputOutputOffset = 0; + + var matches = new List<(int position, string key)>(6); + while (true) + { + matches.Clear(); + + AddMatch(input, PositionString, currentIndexInInput, matches); + AddMatch(input, SpanStartString, currentIndexInInput, matches); + AddMatch(input, SpanEndString, currentIndexInInput, matches); + AddMatch(input, NamedSpanEndString, currentIndexInInput, matches); + + var namedSpanStartMatch = s_namedSpanStartRegex.Match(input, currentIndexInInput); + if (namedSpanStartMatch.Success) + { + matches.Add((namedSpanStartMatch.Index, namedSpanStartMatch.Value)); + } + + var namedSpanEndMatch = s_namedSpanEndRegex.Match(input, currentIndexInInput); + if (namedSpanEndMatch.Success) + { + matches.Add((namedSpanEndMatch.Index, namedSpanEndMatch.Value)); + } + + if (matches.Count == 0) + { + // No more markup to process. + break; + } + + var orderedMatches = matches.OrderBy(t => t.position).ToList(); + if (orderedMatches.Count >= 2 && + endPositionsBuilder.Count < startPositionsBuilder.Count && + matches[0].position == matches[1].position - 1) + { + // We have a slight ambiguity with cases like these: + // + // [|] [|} + // + // Is it starting a new match, or ending an existing match. As a workaround, we + // special case these and consider it ending a match if we have something on the + // stack already. + var (_, _, lastUnmatchedStartKey) = GetLastUnmatchedSpanStart(startPositionsBuilder, endPositionsBuilder); + if ((matches[0].key == SpanStartString && matches[1].key == SpanEndString && lastUnmatchedStartKey.Length == 0) || + (matches[0].key == SpanStartString && matches[1].key == NamedSpanEndString && lastUnmatchedStartKey != string.Empty)) + { + orderedMatches.RemoveAt(0); + } + } + + // Order the matches by their index + var firstMatch = orderedMatches[0]; + + var matchIndexInInput = firstMatch.position; + var matchString = firstMatch.key; + + var matchIndexInOutput = matchIndexInInput - inputOutputOffset; + outputBuilder.Append(input, currentIndexInInput, matchIndexInInput - currentIndexInInput); + + currentIndexInInput = matchIndexInInput + matchString.Length; + inputOutputOffset += matchString.Length; + + switch (matchString.Substring(0, 2)) + { + case PositionString: + positionsBuilder.Add(matchIndexInOutput); + break; + + case SpanStartString: + startPositionsBuilder.Add((matchIndexInInput, matchIndexInOutput, string.Empty)); + break; + + case SpanEndString: + endPositionsBuilder.Add((matchIndexInInput, matchIndexInOutput, string.Empty)); + break; + + case NamedSpanStartString: + var name = namedSpanStartMatch.Groups[1].Value; + startPositionsBuilder.Add((matchIndexInInput, matchIndexInOutput, name)); + break; + + case NamedSpanEndString: + endPositionsBuilder.Add((matchIndexInInput, matchIndexInOutput, string.Empty)); + break; + + case NamedSpanNumberedEndString: + name = namedSpanEndMatch.Groups[1].Value; + endPositionsBuilder.Add((matchIndexInInput, matchIndexInOutput, name)); + break; + + default: + throw new InvalidOperationException(); + } + } + + // Append the remainder of the string. + outputBuilder.Append(input.Substring(currentIndexInInput)); + output = outputBuilder.ToString(); + positions = positionsBuilder.ToImmutable(); + startPositions = startPositionsBuilder.ToImmutable(); + endPositions = endPositionsBuilder.ToImmutable(); + return; + + // Local functions + static (int inputPosition, int outputPosition, string key) GetLastUnmatchedSpanStart(ImmutableArray<(int inputPosition, int outputPosition, string key)>.Builder startPositionsBuilder, ImmutableArray<(int inputPosition, int outputPosition, string key)>.Builder endPositionsBuilder) + { + // For disambiguating [|] and [|}, assume that the start and end tags are behaving like a stack + Debug.Assert(startPositionsBuilder.Count > endPositionsBuilder.Count, "Assertion failed: startPositionsBuilder.Count > endPositionsBuilder.Count"); + + var stackDepth = 0; + var startPositionIndex = startPositionsBuilder.Count - 1; + var endPositionIndex = endPositionsBuilder.Count - 1; + while (true) + { + if (endPositionIndex < 0) + { + // The are no more end tags. Pop the ones remaining on the stack and return the last remaining + // start tag. + return startPositionsBuilder[startPositionIndex - stackDepth]; + } + + if (startPositionsBuilder[startPositionIndex].inputPosition > endPositionsBuilder[endPositionIndex].inputPosition) + { + if (stackDepth == 0) + { + // Reached an unmatched start tag. + return startPositionsBuilder[startPositionIndex]; + } + + // "pop" the start tag off the stack + stackDepth--; + startPositionIndex--; + } + else + { + // "push" the end tag onto the stack + stackDepth++; + endPositionIndex--; + } + } + } + } + + private static void AddMatch(string input, string value, int currentIndex, List<(int index, string value)> matches) + { + var index = input.IndexOf(value, currentIndex); + if (index >= 0) + { + matches.Add((index, value)); + } + } + + public static void GetPositionsAndSpans(string input, out string output, out ImmutableArray positions, out ImmutableDictionary> spans) + { + Parse(input, out output, out positions, out spans); + } + + public static void GetPositionAndSpans(string input, out string output, out int? cursorPosition, out ImmutableDictionary> spans) + { + Parse(input, out output, out var positions, out spans); + cursorPosition = positions.SingleOrNull(); + } + + public static void GetPositionAndSpans(string input, out int? cursorPosition, out ImmutableDictionary> spans) + { + GetPositionAndSpans(input, out _, out cursorPosition, out spans); + } + + public static void GetPositionAndSpans(string input, out string output, out int cursorPosition, out ImmutableDictionary> spans) + { + GetPositionAndSpans(input, out output, out int? cursorPositionOpt, out spans); + cursorPosition = cursorPositionOpt ?? throw new ArgumentException("The input did not include a marked cursor position", nameof(input)); + } + + public static void GetSpans(string input, out string output, out ImmutableDictionary> spans) + { + GetPositionAndSpans(input, out output, out int? _, out spans); + } + + public static void GetPositionAndSpans(string input, out string output, out int? cursorPosition, out ImmutableArray spans) + { + Parse(input, out output, out var positions, out var dictionary); + cursorPosition = positions.SingleOrNull(); + + spans = dictionary.GetValueOrDefault(string.Empty, ImmutableArray.Empty); + } + + public static void GetPositionAndSpans(string input, out int? cursorPosition, out ImmutableArray spans) + { + GetPositionAndSpans(input, out _, out cursorPosition, out spans); + } + + public static void GetPositionAndSpans(string input, out string output, out int cursorPosition, out ImmutableArray spans) + { + GetPositionAndSpans(input, out output, out int? cursorPositionOpt, out spans); + cursorPosition = cursorPositionOpt ?? throw new ArgumentException("The input did not include a marked cursor position", nameof(input)); + } + + /// + /// Process markup containing exactly one position. + /// + /// The input markup. + /// The output, with markup syntax removed. + /// The location of the $$ position in . + /// If does not contain exactly one position, + /// indicated by $$. + public static void GetPosition(string input, out string output, out int cursorPosition) + { + GetPositionAndSpans(input, out output, out cursorPosition, out ImmutableArray _); + } + + public static void GetPositionAndSpan(string input, out string output, out int cursorPosition, out TextSpan span) + { + GetPositionAndSpans(input, out output, out cursorPosition, out ImmutableArray spans); + + span = spans.Single(); + } + + public static void GetSpans(string input, out string output, out ImmutableArray spans) + { + GetPositionAndSpans(input, out output, out int? _, out spans); + } + + public static void GetSpan(string input, out string output, out TextSpan span) + { + GetSpans(input, out output, out ImmutableArray spans); + + span = spans.Single(); + } + + public static string CreateTestFile(string code, int position) + { + return CreateTestFile(code, ImmutableArray.Create(position), ImmutableDictionary>.Empty); + } + + public static string CreateTestFile(string code, int? position, ImmutableArray spans) + { + return CreateTestFile(code, position, ImmutableDictionary>.Empty.Add(string.Empty, spans)); + } + + public static string CreateTestFile(string code, int? position, ImmutableDictionary> spans) + { + var positions = position is object ? ImmutableArray.Create(position.Value) : ImmutableArray.Empty; + return CreateTestFile(code, positions, spans.ToImmutableDictionary(pair => pair.Key, pair => pair.Value.ToImmutableArray())); + } + + public static string CreateTestFile(string code, ImmutableArray positions, ImmutableDictionary> spans) + { + var sb = new StringBuilder(); + var anonymousSpans = spans.GetValueOrDefault(string.Empty, ImmutableArray.Empty); + + for (var i = 0; i <= code.Length; i++) + { + if (positions.Contains(i)) + { + sb.Append(PositionString); + } + + AddSpanString(sb, spans.Where(kvp => kvp.Key != string.Empty), i, start: true); + AddSpanString(sb, spans.Where(kvp => kvp.Key?.Length == 0), i, start: true); + AddSpanString(sb, spans.Where(kvp => kvp.Key?.Length == 0), i, start: false); + AddSpanString(sb, spans.Where(kvp => kvp.Key != string.Empty), i, start: false); + + if (i < code.Length) + { + sb.Append(code[i]); + } + } + + return sb.ToString(); + } + + private static void AddSpanString( + StringBuilder sb, + IEnumerable>> items, + int position, + bool start) + { + foreach (var (name, spans) in items) + { + foreach (var span in spans) + { + if (start && span.Start == position) + { + if (name.Length == 0) + { + sb.Append(SpanStartString); + } + else + { + sb.Append(NamedSpanStartString); + sb.Append(name); + sb.Append(':'); + } + } + else if (!start && span.End == position) + { + if (name.Length == 0) + { + sb.Append(SpanEndString); + } + else + { + sb.Append(NamedSpanEndString); + } + } + } + } + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/TestXmlReferenceResolver.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/TestXmlReferenceResolver.cs new file mode 100644 index 000000000..222de2ac4 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/TestXmlReferenceResolver.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Microsoft.CodeAnalysis.Testing +{ + internal class TestXmlReferenceResolver : XmlReferenceResolver + { + public Dictionary XmlReferences { get; } = + new Dictionary(); + + public override bool Equals(object other) + { + return ReferenceEquals(this, other); + } + + public override int GetHashCode() + { + return RuntimeHelpers.GetHashCode(this); + } + + public override Stream OpenRead(string resolvedPath) + { + if (resolvedPath is null) + { + throw new ArgumentNullException(nameof(resolvedPath)); + } + + if (!XmlReferences.TryGetValue(resolvedPath, out var content)) + { + throw new IOException($"Unable to read XML file: {resolvedPath}"); + } + + return new MemoryStream(Encoding.UTF8.GetBytes(content)); + } + + public override string? ResolveReference(string path, string baseFilePath) + { + if (!XmlReferences.ContainsKey(path)) + { + return null; + } + + return path; + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest/AnalyzerVerifier.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest/AnalyzerVerifier.cs new file mode 100644 index 000000000..cd7769ccd --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest/AnalyzerVerifier.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.MSTest +{ + public static class AnalyzerVerifier + { + public static AnalyzerVerifier Create() + where TAnalyzer : DiagnosticAnalyzer, new() + { + return new AnalyzerVerifier(); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest/AnalyzerVerifier`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest/AnalyzerVerifier`1.cs new file mode 100644 index 000000000..96e02eb3a --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest/AnalyzerVerifier`1.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.MSTest +{ + public class AnalyzerVerifier : CSharpAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + { + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest.csproj new file mode 100644 index 000000000..4146e5a7e --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest.csproj @@ -0,0 +1,27 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest + Microsoft.CodeAnalysis.CSharp.Testing.MSTest + + + + true + Roslyn Analyzer MSTest Framework C# Types. + Roslyn Analyzer MSTest Framework C# Types. + Roslyn Analyzer MSTest Framework C# Types + + + + + + + + + + + + + + diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/AssemblyInfo.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest/PublicAPI.Shipped.txt similarity index 100% rename from src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/AssemblyInfo.vb rename to src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest/PublicAPI.Shipped.txt diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..b627155aa --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +Microsoft.CodeAnalysis.CSharp.Testing.MSTest.AnalyzerVerifier +Microsoft.CodeAnalysis.CSharp.Testing.MSTest.AnalyzerVerifier +Microsoft.CodeAnalysis.CSharp.Testing.MSTest.AnalyzerVerifier.AnalyzerVerifier() -> void +static Microsoft.CodeAnalysis.CSharp.Testing.MSTest.AnalyzerVerifier.Create() -> Microsoft.CodeAnalysis.CSharp.Testing.MSTest.AnalyzerVerifier diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit/AnalyzerVerifier.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit/AnalyzerVerifier.cs new file mode 100644 index 000000000..0a87991e5 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit/AnalyzerVerifier.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.NUnit +{ + public static class AnalyzerVerifier + { + public static AnalyzerVerifier Create() + where TAnalyzer : DiagnosticAnalyzer, new() + { + return new AnalyzerVerifier(); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit/AnalyzerVerifier`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit/AnalyzerVerifier`1.cs new file mode 100644 index 000000000..5dbb746be --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit/AnalyzerVerifier`1.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.NUnit +{ + public class AnalyzerVerifier : CSharpAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + { + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit.csproj new file mode 100644 index 000000000..f02ed764e --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit.csproj @@ -0,0 +1,27 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit + Microsoft.CodeAnalysis.CSharp.Testing.NUnit + + + + true + Roslyn Analyzer NUnit Framework C# Types. + Roslyn Analyzer NUnit Framework C# Types. + Roslyn Analyzer NUnit Framework C# Types + + + + + + + + + + + + + + diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/AssemblyInfo.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit/PublicAPI.Shipped.txt similarity index 100% rename from src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/AssemblyInfo.vb rename to src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit/PublicAPI.Shipped.txt diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..7973ee3a9 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier +Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier +Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier.AnalyzerVerifier() -> void +static Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier.Create() -> Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit/AnalyzerVerifier.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit/AnalyzerVerifier.cs new file mode 100644 index 000000000..8ea438001 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit/AnalyzerVerifier.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.XUnit +{ + public static class AnalyzerVerifier + { + public static AnalyzerVerifier Create() + where TAnalyzer : DiagnosticAnalyzer, new() + { + return new AnalyzerVerifier(); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit/AnalyzerVerifier`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit/AnalyzerVerifier`1.cs new file mode 100644 index 000000000..d73936158 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit/AnalyzerVerifier`1.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.XUnit +{ + public class AnalyzerVerifier : CSharpAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + { + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit.csproj new file mode 100644 index 000000000..346c92a0d --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit.csproj @@ -0,0 +1,27 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit + Microsoft.CodeAnalysis.CSharp.Testing.XUnit + + + + true + Roslyn Analyzer xUnit Framework C# Types. + Roslyn Analyzer xUnit Framework C# Types. + Roslyn Analyzer xUnit Framework C# Types + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..1970e0469 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier +Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier +Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier.AnalyzerVerifier() -> void +static Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier.Create() -> Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing/CSharpAnalyzerTest`2.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing/CSharpAnalyzerTest`2.cs new file mode 100644 index 000000000..51feda59a --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing/CSharpAnalyzerTest`2.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; + +namespace Microsoft.CodeAnalysis.CSharp.Testing +{ + public class CSharpAnalyzerTest : AnalyzerTest + where TAnalyzer : DiagnosticAnalyzer, new() + where TVerifier : IVerifier, new() + { + private static readonly LanguageVersion DefaultLanguageVersion = + Enum.TryParse("Default", out LanguageVersion version) ? version : LanguageVersion.CSharp6; + + 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(DefaultLanguageVersion, DocumentationMode.Diagnose); + + protected override IEnumerable GetDiagnosticAnalyzers() + => new[] { new TAnalyzer() }; + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing/CSharpAnalyzerVerifier`2.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing/CSharpAnalyzerVerifier`2.cs new file mode 100644 index 000000000..09f8a20fe --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing/CSharpAnalyzerVerifier`2.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; + +namespace Microsoft.CodeAnalysis.CSharp.Testing +{ + public class CSharpAnalyzerVerifier : AnalyzerVerifier, TVerifier> + where TAnalyzer : DiagnosticAnalyzer, new() + where TVerifier : IVerifier, new() + { + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.csproj new file mode 100644 index 000000000..c30b89a4c --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.csproj @@ -0,0 +1,25 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.CSharp.Analyzer.Testing + Microsoft.CodeAnalysis.CSharp.Testing + + + + true + Roslyn Analyzer Test Framework C# Types. + Roslyn Analyzer Test Framework C# Types. + Roslyn Analyzer Test Framework C# Types + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..63f1d4430 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing/PublicAPI.Unshipped.txt @@ -0,0 +1,9 @@ +Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerTest +Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerTest.CSharpAnalyzerTest() -> void +Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier +Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier.CSharpAnalyzerVerifier() -> void +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerTest.CreateCompilationOptions() -> Microsoft.CodeAnalysis.CompilationOptions +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerTest.CreateParseOptions() -> Microsoft.CodeAnalysis.ParseOptions +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerTest.DefaultFileExt.get -> string +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerTest.GetDiagnosticAnalyzers() -> System.Collections.Generic.IEnumerable +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerTest.Language.get -> string diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest/CodeFixVerifier.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest/CodeFixVerifier.cs new file mode 100644 index 000000000..6584195c8 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest/CodeFixVerifier.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.MSTest +{ + public static class CodeFixVerifier + { + public static CodeFixVerifier Create() + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + return new CodeFixVerifier(); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest/CodeFixVerifier`2.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest/CodeFixVerifier`2.cs new file mode 100644 index 000000000..dd0884d12 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest/CodeFixVerifier`2.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.MSTest +{ + public class CodeFixVerifier : CSharpCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest.csproj new file mode 100644 index 000000000..3ce725498 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest.csproj @@ -0,0 +1,28 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest + Microsoft.CodeAnalysis.CSharp.Testing.MSTest + + + + true + Roslyn Code Fix MSTest Framework C# Types. + Roslyn Code Fix MSTest Framework C# Types. + Roslyn Code Fix MSTest Framework C# Types + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..8379a584f --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +Microsoft.CodeAnalysis.CSharp.Testing.MSTest.CodeFixVerifier +Microsoft.CodeAnalysis.CSharp.Testing.MSTest.CodeFixVerifier +Microsoft.CodeAnalysis.CSharp.Testing.MSTest.CodeFixVerifier.CodeFixVerifier() -> void +static Microsoft.CodeAnalysis.CSharp.Testing.MSTest.CodeFixVerifier.Create() -> Microsoft.CodeAnalysis.CSharp.Testing.MSTest.CodeFixVerifier diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit/CodeFixVerifier.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit/CodeFixVerifier.cs new file mode 100644 index 000000000..5a5a85fa9 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit/CodeFixVerifier.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.NUnit +{ + public static class CodeFixVerifier + { + public static CodeFixVerifier Create() + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + return new CodeFixVerifier(); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit/CodeFixVerifier`2.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit/CodeFixVerifier`2.cs new file mode 100644 index 000000000..59acf2687 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit/CodeFixVerifier`2.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.NUnit +{ + public class CodeFixVerifier : CSharpCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit.csproj new file mode 100644 index 000000000..4caad97f1 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit.csproj @@ -0,0 +1,28 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit + Microsoft.CodeAnalysis.CSharp.Testing.NUnit + + + + true + Roslyn Code Fix NUnit Framework C# Types. + Roslyn Code Fix NUnit Framework C# Types. + Roslyn Code Fix NUnit Framework C# Types + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..38b1a309d --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +Microsoft.CodeAnalysis.CSharp.Testing.NUnit.CodeFixVerifier +Microsoft.CodeAnalysis.CSharp.Testing.NUnit.CodeFixVerifier +Microsoft.CodeAnalysis.CSharp.Testing.NUnit.CodeFixVerifier.CodeFixVerifier() -> void +static Microsoft.CodeAnalysis.CSharp.Testing.NUnit.CodeFixVerifier.Create() -> Microsoft.CodeAnalysis.CSharp.Testing.NUnit.CodeFixVerifier diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit/CodeFixVerifier.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit/CodeFixVerifier.cs new file mode 100644 index 000000000..9f2502db5 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit/CodeFixVerifier.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.XUnit +{ + public static class CodeFixVerifier + { + public static CodeFixVerifier Create() + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + return new CodeFixVerifier(); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit/CodeFixVerifier`2.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit/CodeFixVerifier`2.cs new file mode 100644 index 000000000..bc330b724 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit/CodeFixVerifier`2.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.XUnit +{ + public class CodeFixVerifier : CSharpCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit.csproj new file mode 100644 index 000000000..63d7a4b3e --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit.csproj @@ -0,0 +1,28 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit + Microsoft.CodeAnalysis.CSharp.Testing.XUnit + + + + true + Roslyn Code Fix xUnit Framework C# Types. + Roslyn Code Fix xUnit Framework C# Types. + Roslyn Code Fix xUnit Framework C# Types + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..a867231de --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +Microsoft.CodeAnalysis.CSharp.Testing.XUnit.CodeFixVerifier +Microsoft.CodeAnalysis.CSharp.Testing.XUnit.CodeFixVerifier +Microsoft.CodeAnalysis.CSharp.Testing.XUnit.CodeFixVerifier.CodeFixVerifier() -> void +static Microsoft.CodeAnalysis.CSharp.Testing.XUnit.CodeFixVerifier.Create() -> Microsoft.CodeAnalysis.CSharp.Testing.XUnit.CodeFixVerifier diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing/CSharpCodeFixTest`3.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing/CSharpCodeFixTest`3.cs new file mode 100644 index 000000000..3f1a2f072 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing/CSharpCodeFixTest`3.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; + +namespace Microsoft.CodeAnalysis.CSharp.Testing +{ + public class CSharpCodeFixTest : CodeFixTest + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + where TVerifier : IVerifier, new() + { + private static readonly LanguageVersion DefaultLanguageVersion = + Enum.TryParse("Default", out LanguageVersion version) ? version : LanguageVersion.CSharp6; + + protected override IEnumerable GetCodeFixProviders() + => new[] { new TCodeFix() }; + + protected override IEnumerable GetDiagnosticAnalyzers() + => new[] { new TAnalyzer() }; + + protected override string DefaultFileExt => "cs"; + + public override string Language => LanguageNames.CSharp; + + public override Type SyntaxKindType => typeof(SyntaxKind); + + protected override CompilationOptions CreateCompilationOptions() + => new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true); + + protected override ParseOptions CreateParseOptions() + => new CSharpParseOptions(DefaultLanguageVersion, DocumentationMode.Diagnose); + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing/CSharpCodeFixVerifier`3.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing/CSharpCodeFixVerifier`3.cs new file mode 100644 index 000000000..46ba7c305 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing/CSharpCodeFixVerifier`3.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; + +namespace Microsoft.CodeAnalysis.CSharp.Testing +{ + public class CSharpCodeFixVerifier : CodeFixVerifier, TVerifier> + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + where TVerifier : IVerifier, new() + { + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.csproj new file mode 100644 index 000000000..526f23590 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.csproj @@ -0,0 +1,25 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.CSharp.CodeFix.Testing + Microsoft.CodeAnalysis.CSharp.Testing + + + + true + Roslyn Code Fix Framework C# Types. + Roslyn Code Fix Framework C# Types. + Roslyn Code Fix Framework C# Types + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..84805ec41 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing/PublicAPI.Unshipped.txt @@ -0,0 +1,11 @@ +Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixTest +Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixTest.CSharpCodeFixTest() -> void +Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier +Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier.CSharpCodeFixVerifier() -> void +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixTest.CreateCompilationOptions() -> Microsoft.CodeAnalysis.CompilationOptions +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixTest.CreateParseOptions() -> Microsoft.CodeAnalysis.ParseOptions +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixTest.DefaultFileExt.get -> string +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixTest.GetCodeFixProviders() -> System.Collections.Generic.IEnumerable +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixTest.GetDiagnosticAnalyzers() -> System.Collections.Generic.IEnumerable +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixTest.Language.get -> string +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixTest.SyntaxKindType.get -> System.Type diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest/CodeRefactoringVerifier.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest/CodeRefactoringVerifier.cs new file mode 100644 index 000000000..545e1d68d --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest/CodeRefactoringVerifier.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeRefactorings; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.MSTest +{ + public static class CodeRefactoringVerifier + { + public static CodeRefactoringVerifier Create() + where TCodeRefactoring : CodeRefactoringProvider, new() + { + return new CodeRefactoringVerifier(); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest/CodeRefactoringVerifier`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest/CodeRefactoringVerifier`1.cs new file mode 100644 index 000000000..98b8d6f65 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest/CodeRefactoringVerifier`1.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.MSTest +{ + public class CodeRefactoringVerifier : CSharpCodeRefactoringVerifier + where TCodeRefactoring : CodeRefactoringProvider, new() + { + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest.csproj new file mode 100644 index 000000000..fee75c51f --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest.csproj @@ -0,0 +1,27 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.CSharp.Testing.MSTest + + + + true + Roslyn Code Refactoring MSTest Framework C# Types. + Roslyn Code Refactoring MSTest Framework C# Types. + Roslyn Code Refactoring MSTest Framework C# Types + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..96d971a0a --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +Microsoft.CodeAnalysis.CSharp.Testing.MSTest.CodeRefactoringVerifier +Microsoft.CodeAnalysis.CSharp.Testing.MSTest.CodeRefactoringVerifier +Microsoft.CodeAnalysis.CSharp.Testing.MSTest.CodeRefactoringVerifier.CodeRefactoringVerifier() -> void +static Microsoft.CodeAnalysis.CSharp.Testing.MSTest.CodeRefactoringVerifier.Create() -> Microsoft.CodeAnalysis.CSharp.Testing.MSTest.CodeRefactoringVerifier diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit/CodeRefactoringVerifier.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit/CodeRefactoringVerifier.cs new file mode 100644 index 000000000..390c991f5 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit/CodeRefactoringVerifier.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeRefactorings; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.NUnit +{ + public static class CodeRefactoringVerifier + { + public static CodeRefactoringVerifier Create() + where TCodeRefactoring : CodeRefactoringProvider, new() + { + return new CodeRefactoringVerifier(); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit/CodeRefactoringVerifier`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit/CodeRefactoringVerifier`1.cs new file mode 100644 index 000000000..b60767361 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit/CodeRefactoringVerifier`1.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.NUnit +{ + public class CodeRefactoringVerifier : CSharpCodeRefactoringVerifier + where TCodeRefactoring : CodeRefactoringProvider, new() + { + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit.csproj new file mode 100644 index 000000000..3c86e51ea --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit.csproj @@ -0,0 +1,27 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.CSharp.Testing.NUnit + + + + true + Roslyn Code Refactoring NUnit Framework C# Types. + Roslyn Code Refactoring NUnit Framework C# Types. + Roslyn Code Refactoring NUnit Framework C# Types + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..f2d270f35 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +Microsoft.CodeAnalysis.CSharp.Testing.NUnit.CodeRefactoringVerifier +Microsoft.CodeAnalysis.CSharp.Testing.NUnit.CodeRefactoringVerifier +Microsoft.CodeAnalysis.CSharp.Testing.NUnit.CodeRefactoringVerifier.CodeRefactoringVerifier() -> void +static Microsoft.CodeAnalysis.CSharp.Testing.NUnit.CodeRefactoringVerifier.Create() -> Microsoft.CodeAnalysis.CSharp.Testing.NUnit.CodeRefactoringVerifier diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit/CodeRefactoringVerifier.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit/CodeRefactoringVerifier.cs new file mode 100644 index 000000000..6d0a36979 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit/CodeRefactoringVerifier.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeRefactorings; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.XUnit +{ + public static class CodeRefactoringVerifier + { + public static CodeRefactoringVerifier Create() + where TCodeRefactoring : CodeRefactoringProvider, new() + { + return new CodeRefactoringVerifier(); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit/CodeRefactoringVerifier`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit/CodeRefactoringVerifier`1.cs new file mode 100644 index 000000000..8d0c0aa8f --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit/CodeRefactoringVerifier`1.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.XUnit +{ + public class CodeRefactoringVerifier : CSharpCodeRefactoringVerifier + where TCodeRefactoring : CodeRefactoringProvider, new() + { + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit.csproj new file mode 100644 index 000000000..d59bcca56 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit.csproj @@ -0,0 +1,27 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.CSharp.Testing.XUnit + + + + true + Roslyn Code Refactoring xUnit Framework C# Types. + Roslyn Code Refactoring xUnit Framework C# Types. + Roslyn Code Refactoring xUnit Framework C# Types + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..55a94d733 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +Microsoft.CodeAnalysis.CSharp.Testing.XUnit.CodeRefactoringVerifier +Microsoft.CodeAnalysis.CSharp.Testing.XUnit.CodeRefactoringVerifier +Microsoft.CodeAnalysis.CSharp.Testing.XUnit.CodeRefactoringVerifier.CodeRefactoringVerifier() -> void +static Microsoft.CodeAnalysis.CSharp.Testing.XUnit.CodeRefactoringVerifier.Create() -> Microsoft.CodeAnalysis.CSharp.Testing.XUnit.CodeRefactoringVerifier diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing/CSharpCodeRefactoringTest`2.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing/CSharpCodeRefactoringTest`2.cs new file mode 100644 index 000000000..d91fc8310 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing/CSharpCodeRefactoringTest`2.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Testing; + +namespace Microsoft.CodeAnalysis.CSharp.Testing +{ + public class CSharpCodeRefactoringTest : CodeRefactoringTest + where TCodeRefactoring : CodeRefactoringProvider, new() + where TVerifier : IVerifier, new() + { + private static readonly LanguageVersion DefaultLanguageVersion = + Enum.TryParse("Default", out LanguageVersion version) ? version : LanguageVersion.CSharp6; + + protected override IEnumerable GetCodeRefactoringProviders() + => new[] { new TCodeRefactoring() }; + + protected override string DefaultFileExt => "cs"; + + public override string Language => LanguageNames.CSharp; + + public override Type SyntaxKindType => typeof(SyntaxKind); + + protected override CompilationOptions CreateCompilationOptions() + => new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true); + + protected override ParseOptions CreateParseOptions() + => new CSharpParseOptions(DefaultLanguageVersion, DocumentationMode.Diagnose); + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing/CSharpCodeRefactoringVerifier`2.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing/CSharpCodeRefactoringVerifier`2.cs new file mode 100644 index 000000000..f9ef679b0 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing/CSharpCodeRefactoringVerifier`2.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Testing; + +namespace Microsoft.CodeAnalysis.CSharp.Testing +{ + public class CSharpCodeRefactoringVerifier : CodeRefactoringVerifier, TVerifier> + where TCodeRefactoring : CodeRefactoringProvider, new() + where TVerifier : IVerifier, new() + { + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.csproj new file mode 100644 index 000000000..a5b765f6b --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.csproj @@ -0,0 +1,25 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing + Microsoft.CodeAnalysis.CSharp.Testing + + + + true + Roslyn Code Refactoring Framework C# Types. + Roslyn Code Refactoring Framework C# Types. + Roslyn Code Refactoring Framework C# Types + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..e5879c908 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing/PublicAPI.Unshipped.txt @@ -0,0 +1,10 @@ +Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeRefactoringTest +Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeRefactoringTest.CSharpCodeRefactoringTest() -> void +Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeRefactoringVerifier +Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeRefactoringVerifier.CSharpCodeRefactoringVerifier() -> void +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeRefactoringTest.CreateCompilationOptions() -> Microsoft.CodeAnalysis.CompilationOptions +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeRefactoringTest.CreateParseOptions() -> Microsoft.CodeAnalysis.ParseOptions +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeRefactoringTest.DefaultFileExt.get -> string +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeRefactoringTest.GetCodeRefactoringProviders() -> System.Collections.Generic.IEnumerable +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeRefactoringTest.Language.get -> string +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeRefactoringTest.SyntaxKindType.get -> System.Type diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest.csproj new file mode 100644 index 000000000..0593fa981 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest.csproj @@ -0,0 +1,27 @@ + + + + $(TestingLibrarySourceGeneratorTargetFrameworks) + Microsoft.CodeAnalysis.CSharp.Testing.MSTest + + + + true + Roslyn Source Generator MSTest Framework C# Types. + Roslyn Source Generator MSTest Framework C# Types. + Roslyn Source Generator MSTest Framework C# Types + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..6a1123a97 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +Microsoft.CodeAnalysis.CSharp.Testing.MSTest.SourceGeneratorVerifier +Microsoft.CodeAnalysis.CSharp.Testing.MSTest.SourceGeneratorVerifier +Microsoft.CodeAnalysis.CSharp.Testing.MSTest.SourceGeneratorVerifier.SourceGeneratorVerifier() -> void +static Microsoft.CodeAnalysis.CSharp.Testing.MSTest.SourceGeneratorVerifier.Create() -> Microsoft.CodeAnalysis.CSharp.Testing.MSTest.SourceGeneratorVerifier diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest/SourceGeneratorVerifier.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest/SourceGeneratorVerifier.cs new file mode 100644 index 000000000..95ab35185 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest/SourceGeneratorVerifier.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.CSharp.Testing.MSTest +{ + public static class SourceGeneratorVerifier + { + public static SourceGeneratorVerifier Create() + where TSourceGenerator : ISourceGenerator, new() + { + return new SourceGeneratorVerifier(); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest/SourceGeneratorVerifier`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest/SourceGeneratorVerifier`1.cs new file mode 100644 index 000000000..4f852ac23 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest/SourceGeneratorVerifier`1.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.MSTest +{ + public class SourceGeneratorVerifier : CSharpSourceGeneratorVerifier + where TSourceGenerator : ISourceGenerator, new() + { + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit.csproj new file mode 100644 index 000000000..3fbaf55db --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit.csproj @@ -0,0 +1,27 @@ + + + + $(TestingLibrarySourceGeneratorTargetFrameworks) + Microsoft.CodeAnalysis.CSharp.Testing.NUnit + + + + true + Roslyn Source Generator NUnit Framework C# Types. + Roslyn Source Generator NUnit Framework C# Types. + Roslyn Source Generator NUnit Framework C# Types + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..e6f762ae9 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +Microsoft.CodeAnalysis.CSharp.Testing.NUnit.SourceGeneratorVerifier +Microsoft.CodeAnalysis.CSharp.Testing.NUnit.SourceGeneratorVerifier +Microsoft.CodeAnalysis.CSharp.Testing.NUnit.SourceGeneratorVerifier.SourceGeneratorVerifier() -> void +static Microsoft.CodeAnalysis.CSharp.Testing.NUnit.SourceGeneratorVerifier.Create() -> Microsoft.CodeAnalysis.CSharp.Testing.NUnit.SourceGeneratorVerifier diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit/SourceGeneratorVerifier.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit/SourceGeneratorVerifier.cs new file mode 100644 index 000000000..078259b7b --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit/SourceGeneratorVerifier.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.CSharp.Testing.NUnit +{ + public static class SourceGeneratorVerifier + { + public static SourceGeneratorVerifier Create() + where TSourceGenerator : ISourceGenerator, new() + { + return new SourceGeneratorVerifier(); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit/SourceGeneratorVerifier`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit/SourceGeneratorVerifier`1.cs new file mode 100644 index 000000000..e0900dcba --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit/SourceGeneratorVerifier`1.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.NUnit +{ + public class SourceGeneratorVerifier : CSharpSourceGeneratorVerifier + where TSourceGenerator : ISourceGenerator, new() + { + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit.csproj new file mode 100644 index 000000000..435280807 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit.csproj @@ -0,0 +1,27 @@ + + + + $(TestingLibrarySourceGeneratorTargetFrameworks) + Microsoft.CodeAnalysis.CSharp.Testing.XUnit + + + + true + Roslyn Source Generator xUnit Framework C# Types. + Roslyn Source Generator xUnit Framework C# Types. + Roslyn Source Generator xUnit Framework C# Types + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..d625f5b80 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit/PublicAPI.Unshipped.txt @@ -0,0 +1,4 @@ +Microsoft.CodeAnalysis.CSharp.Testing.XUnit.SourceGeneratorVerifier +Microsoft.CodeAnalysis.CSharp.Testing.XUnit.SourceGeneratorVerifier +Microsoft.CodeAnalysis.CSharp.Testing.XUnit.SourceGeneratorVerifier.SourceGeneratorVerifier() -> void +static Microsoft.CodeAnalysis.CSharp.Testing.XUnit.SourceGeneratorVerifier.Create() -> Microsoft.CodeAnalysis.CSharp.Testing.XUnit.SourceGeneratorVerifier diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit/SourceGeneratorVerifier.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit/SourceGeneratorVerifier.cs new file mode 100644 index 000000000..1e605119d --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit/SourceGeneratorVerifier.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.CSharp.Testing.XUnit +{ + public static class SourceGeneratorVerifier + { + public static SourceGeneratorVerifier Create() + where TSourceGenerator : ISourceGenerator, new() + { + return new SourceGeneratorVerifier(); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit/SourceGeneratorVerifier`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit/SourceGeneratorVerifier`1.cs new file mode 100644 index 000000000..2b7511eaf --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit/SourceGeneratorVerifier`1.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace Microsoft.CodeAnalysis.CSharp.Testing.XUnit +{ + public class SourceGeneratorVerifier : CSharpSourceGeneratorVerifier + where TSourceGenerator : ISourceGenerator, new() + { + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing/CSharpSourceGeneratorTest`2.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing/CSharpSourceGeneratorTest`2.cs new file mode 100644 index 000000000..be038cda1 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing/CSharpSourceGeneratorTest`2.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Testing; + +namespace Microsoft.CodeAnalysis.CSharp.Testing +{ + public class CSharpSourceGeneratorTest : SourceGeneratorTest + where TSourceGenerator : ISourceGenerator, new() + where TVerifier : IVerifier, new() + { + private static readonly LanguageVersion DefaultLanguageVersion = + Enum.TryParse("Default", out LanguageVersion version) ? version : LanguageVersion.CSharp6; + + protected override IEnumerable GetSourceGenerators() + => new ISourceGenerator[] { new TSourceGenerator() }; + + protected override string DefaultFileExt => "cs"; + + public override string Language => LanguageNames.CSharp; + + protected override GeneratorDriver CreateGeneratorDriver(Project project, ImmutableArray sourceGenerators) + { + return CSharpGeneratorDriver.Create( + sourceGenerators, + project.AnalyzerOptions.AdditionalFiles, + (CSharpParseOptions)project.ParseOptions!, + project.AnalyzerOptions.AnalyzerConfigOptionsProvider); + } + + protected override CompilationOptions CreateCompilationOptions() + => new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true); + + protected override ParseOptions CreateParseOptions() + => new CSharpParseOptions(DefaultLanguageVersion, DocumentationMode.Diagnose); + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing/CSharpSourceGeneratorVerifier`2.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing/CSharpSourceGeneratorVerifier`2.cs new file mode 100644 index 000000000..4d5ac83ce --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing/CSharpSourceGeneratorVerifier`2.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Testing; + +namespace Microsoft.CodeAnalysis.CSharp.Testing +{ + public class CSharpSourceGeneratorVerifier : SourceGeneratorVerifier, TVerifier> + where TSourceGenerator : ISourceGenerator, new() + where TVerifier : IVerifier, new() + { + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.csproj new file mode 100644 index 000000000..a1472a2b2 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.csproj @@ -0,0 +1,25 @@ + + + + $(TestingLibrarySourceGeneratorTargetFrameworks) + Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing + Microsoft.CodeAnalysis.CSharp.Testing + + + + true + Roslyn Source Generator Framework C# Types. + Roslyn Source Generator Framework C# Types. + Roslyn Source Generator Framework C# Types + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..c2fa56484 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing/PublicAPI.Unshipped.txt @@ -0,0 +1,10 @@ +Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest +Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest.CSharpSourceGeneratorTest() -> void +Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorVerifier +Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorVerifier.CSharpSourceGeneratorVerifier() -> void +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest.CreateCompilationOptions() -> Microsoft.CodeAnalysis.CompilationOptions +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest.CreateGeneratorDriver(Microsoft.CodeAnalysis.Project project, System.Collections.Immutable.ImmutableArray sourceGenerators) -> Microsoft.CodeAnalysis.GeneratorDriver +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest.CreateParseOptions() -> Microsoft.CodeAnalysis.ParseOptions +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest.DefaultFileExt.get -> string +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest.GetSourceGenerators() -> System.Collections.Generic.IEnumerable +override Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest.Language.get -> string diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/CodeFixTestBehaviors.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/CodeFixTestBehaviors.cs new file mode 100644 index 000000000..ba33b5be9 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/CodeFixTestBehaviors.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Options for customizing code fix test behaviors. + /// + [Flags] + public enum CodeFixTestBehaviors + { + /// + /// No special behaviors apply. + /// + None = 0, + + /// + /// Skip the Fix All in Document check. + /// + SkipFixAllInDocumentCheck = 1 << 0, + + /// + /// Skip the Fix All in Project check. + /// + SkipFixAllInProjectCheck = 1 << 1, + + /// + /// Skip the Fix All in Solution check. + /// + SkipFixAllInSolutionCheck = 1 << 2, + + /// + /// Skip all Fix All checks. + /// + SkipFixAllCheck = SkipFixAllInDocumentCheck | SkipFixAllInProjectCheck | SkipFixAllInSolutionCheck, + + /// + /// One run one code fix iteration. + /// + FixOne = 1 << 3, + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/CodeFixTest`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/CodeFixTest`1.cs new file mode 100644 index 000000000..0bc34d839 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/CodeFixTest`1.cs @@ -0,0 +1,859 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Runtime.ExceptionServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Testing.Model; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Testing +{ + public abstract class CodeFixTest : CodeActionTest + where TVerifier : IVerifier, new() + { + /// + [Obsolete("Use " + nameof(CodeActionIndex) + " instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public int? CodeFixIndex + { + get => CodeActionIndex; + set => CodeActionIndex = value; + } + + /// + [Obsolete("Use " + nameof(CodeActionEquivalenceKey) + " instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public string? CodeFixEquivalenceKey + { + get => CodeActionEquivalenceKey; + set => CodeActionEquivalenceKey = value; + } + + /// + /// Sets the expected output source file for code fix testing. + /// + /// + public string FixedCode + { + set + { + if (value != null) + { + FixedState.Sources.Add(value); + } + } + } + + public SolutionState FixedState { get; } + + /// + /// Sets the expected output source file after a Fix All operation is applied. + /// + /// + public string BatchFixedCode + { + set + { + if (value != null) + { + BatchFixedState.Sources.Add(value); + } + } + } + + public SolutionState BatchFixedState { get; } + + /// + /// Gets or sets the number of code fix iterations expected during code fix testing. + /// + /// + /// Code fixes are applied until one of the following conditions are met: + /// + /// + /// No diagnostics are reported in the input. + /// No code fixes are provided for the diagnostics reported in the input. + /// The code fix applied for the diagnostics does not produce a change in the source file(s). + /// The maximum number of allowed iterations is exceeded. + /// + /// + /// If the number of iterations is positive, it represents an exact number of iterations: code fix tests + /// will fail if the code fix required more or fewer iterations to complete. If the number of iterations is + /// negative, the negation of the number of iterations is treated as an upper bound on the number of allowed + /// iterations: code fix tests will fail only if the code fix required more iterations to complete. If the + /// number of iterations is zero, the code fix test will validate that no code fixes are offered for the set of + /// diagnostics reported in the original input. + /// + /// When the number of iterations is not specified, the value is automatically selected according to the + /// current test configuration: + /// + /// + /// If the expected code fix output equals the input sources, the default value is treated as 0. + /// Otherwise, the default value is treated as the negative of the number of fixable diagnostics appearing in the input source file(s). + /// + /// + /// + /// The default value for this property can be interpreted as "Iterative code fix operations are expected + /// to complete after at most one operation for each fixable diagnostic in the input source has been applied. + /// Completing in fewer iterations is acceptable." + /// + /// + public int? NumberOfIncrementalIterations { get; set; } + + /// + /// Gets or sets the number of code fix iterations expected during code fix testing for Fix All scenarios. + /// + /// + /// See the property for an overview of the behavior of this + /// property. If the number of Fix All iterations is not specified, the value is automatically selected + /// according to the current test configuration: + /// + /// + /// If the expected Fix All output equals the input sources, the default value is treated as 0. + /// If all projects in the solution have the same , the default value is treated as 1. + /// Otherwise, the default value is treated as new negative of the number of languages represented by projects in the solution. + /// + /// + /// + /// The default value for this property can be interpreted as "Fix All operations are expected to complete + /// in the minimum number of iterations possible unless otherwise specified." + /// + /// + /// + public int? NumberOfFixAllIterations { get; set; } + + /// + /// Gets or sets the number of code fix iterations expected during code fix testing for Fix All in Document + /// scenarios. + /// + /// + /// See the property for an overview of the behavior of this + /// property. If the number of Fix All in Document iterations is not specified, the value is automatically + /// selected according to the current test configuration: + /// + /// + /// If a value has been explicitly provided for , the value is used as-is. + /// If the expected Fix All output equals the input sources, the default value is treated as 0. + /// Otherwise, the default value is treated as the negative of the number of distinct documents containing fixable diagnostics (typically -1). + /// + /// + /// + /// The default value for this property can be interpreted as "Fix All in Document operations are expected + /// to complete after at most one operation for each fixable document in the input source has been applied. + /// Completing in fewer iterations is acceptable." + /// + /// + /// + /// + public int? NumberOfFixAllInDocumentIterations { get; set; } + + /// + /// Gets or sets the number of code fix iterations expected during code fix testing for Fix All in Project + /// scenarios. + /// + /// + /// See the property for an overview of the behavior of this + /// property. If the number of Fix All in Project iterations is not specified, the value is automatically + /// selected according to the current test configuration: + /// + /// + /// If a value has been explicitly provided for , the value is used as-is. + /// If the expected Fix All output equals the input sources, the default value is treated as 0. + /// Otherwise, the default value is treated as the negative of the number of distinct projects containing fixable diagnostics (typically -1). + /// + /// + /// + /// The default value for this property can be interpreted as "Fix All in Project operations are expected + /// to complete after at most one operation for each fixable project in the input source has been applied. + /// Completing in fewer iterations is acceptable." + /// + /// + /// + /// + public int? NumberOfFixAllInProjectIterations { get; set; } + + /// + /// Gets or sets the code fix test behaviors applying to this test. The default value is + /// . + /// + public CodeFixTestBehaviors CodeFixTestBehaviors { get; set; } + + /// + [Obsolete("Use " + nameof(CodeActionValidationMode) + " instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public CodeFixValidationMode CodeFixValidationMode + { + get => CodeActionValidationMode switch + { + CodeActionValidationMode.None => CodeFixValidationMode.None, + CodeActionValidationMode.SemanticStructure => CodeFixValidationMode.SemanticStructure, + CodeActionValidationMode.Full => CodeFixValidationMode.Full, + _ => throw new InvalidOperationException(), + }; + + set => CodeActionValidationMode = value switch + { + CodeFixValidationMode.None => CodeActionValidationMode.None, + CodeFixValidationMode.SemanticStructure => CodeActionValidationMode.SemanticStructure, + CodeFixValidationMode.Full => CodeActionValidationMode.Full, + _ => throw new ArgumentOutOfRangeException(nameof(value)), + }; + } + + protected CodeFixTest() + { + FixedState = new SolutionState(DefaultTestProjectName, Language, DefaultFilePathPrefix, DefaultFileExt); + BatchFixedState = new SolutionState(DefaultTestProjectName, Language, DefaultFilePathPrefix, DefaultFileExt); + } + + /// + /// Returns the code fixes being tested - to be implemented in non-abstract class. + /// + /// The to be used. + protected abstract IEnumerable GetCodeFixProviders(); + + /// + protected override bool IsCompilerDiagnosticIncluded(Diagnostic diagnostic, CompilerDiagnostics compilerDiagnostics) + { + if (base.IsCompilerDiagnosticIncluded(diagnostic, compilerDiagnostics)) + { + return true; + } + + return CodeFixProvidersHandleDiagnostic(diagnostic); + + bool CodeFixProvidersHandleDiagnostic(Diagnostic localDiagnostic) + { + var codeFixProviders = GetCodeFixProviders(); + return codeFixProviders + .Any(provider => provider.FixableDiagnosticIds.Any(fixerDiagnosticId => string.Equals(fixerDiagnosticId, localDiagnostic.Id, StringComparison.OrdinalIgnoreCase))); + } + } + + protected override async Task RunImplAsync(CancellationToken cancellationToken) + { + Verify.NotEmpty($"{nameof(TestState)}.{nameof(SolutionState.Sources)}", TestState.Sources); + + var analyzers = GetDiagnosticAnalyzers().ToArray(); + var defaultDiagnostic = GetDefaultDiagnostic(analyzers); + var supportedDiagnostics = analyzers.SelectMany(analyzer => analyzer.SupportedDiagnostics).ToImmutableArray(); + var fixableDiagnostics = GetCodeFixProviders().SelectMany(provider => provider.FixableDiagnosticIds).ToImmutableArray(); + + var rawTestState = TestState.WithInheritedValuesApplied(null, fixableDiagnostics); + var rawFixedState = FixedState.WithInheritedValuesApplied(rawTestState, fixableDiagnostics); + var rawBatchFixedState = BatchFixedState.WithInheritedValuesApplied(rawFixedState, fixableDiagnostics); + + var testState = rawTestState.WithProcessedMarkup(MarkupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, DefaultFilePath); + var fixedState = rawFixedState.WithProcessedMarkup(MarkupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, DefaultFilePath); + var batchFixedState = rawBatchFixedState.WithProcessedMarkup(MarkupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, DefaultFilePath); + + var allowFixAll = (CodeFixTestBehaviors & CodeFixTestBehaviors.SkipFixAllCheck) != CodeFixTestBehaviors.SkipFixAllCheck; + + await VerifyDiagnosticsAsync(new EvaluatedProjectState(testState, ReferenceAssemblies), testState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), testState.ExpectedDiagnostics.ToArray(), Verify.PushContext("Diagnostics of test state"), cancellationToken).ConfigureAwait(false); + + if (CodeFixExpected()) + { + await VerifyDiagnosticsAsync(new EvaluatedProjectState(fixedState, ReferenceAssemblies), fixedState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), fixedState.ExpectedDiagnostics.ToArray(), Verify.PushContext("Diagnostics of fixed state"), cancellationToken).ConfigureAwait(false); + if (allowFixAll && CodeActionExpected(BatchFixedState)) + { + await VerifyDiagnosticsAsync(new EvaluatedProjectState(batchFixedState, ReferenceAssemblies), batchFixedState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), batchFixedState.ExpectedDiagnostics.ToArray(), Verify.PushContext("Diagnostics of batch fixed state"), cancellationToken).ConfigureAwait(false); + } + + await VerifyFixAsync(testState, fixedState, batchFixedState, Verify, cancellationToken).ConfigureAwait(false); + } + } + + private bool CodeFixExpected() + { + return CodeActionExpected(FixedState) + || CodeActionExpected(BatchFixedState); + } + + /// + /// Called to test a C# code fix when applied on the input source as a string. + /// + /// The effective input test state. + /// The effective test state after incremental code fixes are applied. + /// The effective test state after batch code fixes are applied. + /// The verifier to use for test assertions. + /// The that the task will observe. + /// A representing the asynchronous operation. + protected async Task VerifyFixAsync(SolutionState testState, SolutionState fixedState, SolutionState batchFixedState, IVerifier verifier, CancellationToken cancellationToken) + { + var fixers = GetCodeFixProviders().ToImmutableArray(); + var fixableDiagnostics = testState.ExpectedDiagnostics.Where(diagnostic => fixers.Any(fixer => fixer.FixableDiagnosticIds.Contains(diagnostic.Id))).ToImmutableArray(); + + int numberOfIncrementalIterations; + int numberOfFixAllIterations; + int numberOfFixAllInProjectIterations; + int numberOfFixAllInDocumentIterations; + if (NumberOfIncrementalIterations != null) + { + numberOfIncrementalIterations = NumberOfIncrementalIterations.Value; + } + else + { + if (!HasAnyChange(testState, fixedState, recursive: true)) + { + numberOfIncrementalIterations = 0; + } + else + { + // Expect at most one iteration per fixable diagnostic + numberOfIncrementalIterations = -fixableDiagnostics.Count(); + } + } + + if (NumberOfFixAllIterations != null) + { + numberOfFixAllIterations = NumberOfFixAllIterations.Value; + } + else + { + if (!HasAnyChange(testState, batchFixedState, recursive: true)) + { + numberOfFixAllIterations = 0; + } + else + { + // Expect at most one iteration per language with fixable diagnostics. Since we can't tell the + // language from ExpectedDiagnostic, use a conservative value from the number of project languages + // present. + numberOfFixAllIterations = -Enumerable.Repeat(testState.Language, 1).Concat(testState.AdditionalProjects.Select(p => p.Value.Language)).Distinct().Count(); + } + } + + if (NumberOfFixAllInProjectIterations != null) + { + numberOfFixAllInProjectIterations = NumberOfFixAllInProjectIterations.Value; + } + else if (NumberOfFixAllIterations != null) + { + numberOfFixAllInProjectIterations = NumberOfFixAllIterations.Value; + } + else + { + numberOfFixAllInProjectIterations = 0; + if (HasAnyChange(testState, batchFixedState, recursive: false)) + { + // Expect at most one iteration for a fixable primary project + numberOfFixAllInProjectIterations--; + } + + foreach (var (name, state) in testState.AdditionalProjects) + { + if (!batchFixedState.AdditionalProjects.TryGetValue(name, out var expected) + || HasAnyChange(state, expected, recursive: true)) + { + // Expect at most one iteration for each fixable additional project + numberOfFixAllInProjectIterations--; + } + } + } + + if (NumberOfFixAllInDocumentIterations != null) + { + numberOfFixAllInDocumentIterations = NumberOfFixAllInDocumentIterations.Value; + } + else if (NumberOfFixAllIterations != null) + { + numberOfFixAllInDocumentIterations = NumberOfFixAllIterations.Value; + } + else + { + if (!HasAnyChange(testState, batchFixedState, recursive: false)) + { + numberOfFixAllInDocumentIterations = 0; + } + else + { + // Expect at most one iteration per fixable document + numberOfFixAllInDocumentIterations = -fixableDiagnostics.GroupBy(diagnostic => diagnostic.Spans.FirstOrDefault().Span.Path).Count(); + } + } + + var t1 = VerifyFixAsync(Language, GetDiagnosticAnalyzers().ToImmutableArray(), GetCodeFixProviders().ToImmutableArray(), testState, fixedState, numberOfIncrementalIterations, FixEachAnalyzerDiagnosticAsync, verifier.PushContext("Iterative code fix application"), cancellationToken).ConfigureAwait(false); + + var fixAllProvider = GetCodeFixProviders().Select(codeFixProvider => codeFixProvider.GetFixAllProvider()).Where(codeFixProvider => codeFixProvider != null).ToImmutableArray(); + + if (fixAllProvider.IsEmpty) + { + await t1; + } + else + { + if (Debugger.IsAttached) + { + await t1; + } + + var t2 = CodeFixTestBehaviors.HasFlag(CodeFixTestBehaviors.SkipFixAllInDocumentCheck) + ? ((Task)Task.FromResult(true)).ConfigureAwait(false) + : VerifyFixAsync(Language, GetDiagnosticAnalyzers().ToImmutableArray(), GetCodeFixProviders().ToImmutableArray(), testState, batchFixedState, numberOfFixAllInDocumentIterations, FixAllAnalyzerDiagnosticsInDocumentAsync, verifier.PushContext("Fix all in document"), cancellationToken).ConfigureAwait(false); + if (Debugger.IsAttached) + { + await t2; + } + + var t3 = CodeFixTestBehaviors.HasFlag(CodeFixTestBehaviors.SkipFixAllInProjectCheck) + ? ((Task)Task.FromResult(true)).ConfigureAwait(false) + : VerifyFixAsync(Language, GetDiagnosticAnalyzers().ToImmutableArray(), GetCodeFixProviders().ToImmutableArray(), testState, batchFixedState, numberOfFixAllInProjectIterations, FixAllAnalyzerDiagnosticsInProjectAsync, verifier.PushContext("Fix all in project"), cancellationToken).ConfigureAwait(false); + if (Debugger.IsAttached) + { + await t3; + } + + var t4 = CodeFixTestBehaviors.HasFlag(CodeFixTestBehaviors.SkipFixAllInSolutionCheck) + ? ((Task)Task.FromResult(true)).ConfigureAwait(false) + : VerifyFixAsync(Language, GetDiagnosticAnalyzers().ToImmutableArray(), GetCodeFixProviders().ToImmutableArray(), testState, batchFixedState, numberOfFixAllIterations, FixAllAnalyzerDiagnosticsInSolutionAsync, verifier.PushContext("Fix all in solution"), cancellationToken).ConfigureAwait(false); + if (Debugger.IsAttached) + { + await t4; + } + + if (!Debugger.IsAttached) + { + // Allow the operations to run in parallel + await t1; + await t2; + await t3; + await t4; + } + } + } + + /// + /// Selects the diagnostic to fix when is used. + /// + /// The diagnostics available for fixing. + /// The diagnostic to fix; otherwise, if no diagnostics should be fixed. + protected virtual Diagnostic? TrySelectDiagnosticToFix(ImmutableArray fixableDiagnostics) + { + return fixableDiagnostics.FirstOrDefault(); + } + + private async Task VerifyFixAsync( + string language, + ImmutableArray analyzers, + ImmutableArray codeFixProviders, + SolutionState oldState, + SolutionState newState, + int numberOfIterations, + Func, ImmutableArray, int?, string?, Action?, Project, int, IVerifier, CancellationToken, Task<(Project project, ExceptionDispatchInfo? iterationCountFailure)>> getFixedProject, + IVerifier verifier, + CancellationToken cancellationToken) + { + var project = await CreateProjectAsync(new EvaluatedProjectState(oldState, ReferenceAssemblies), oldState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), cancellationToken); + var compilerDiagnostics = await GetCompilerDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); + + ExceptionDispatchInfo? iterationCountFailure; + (project, iterationCountFailure) = await getFixedProject(analyzers, codeFixProviders, CodeActionIndex, CodeActionEquivalenceKey, CodeActionVerifier, project, numberOfIterations, verifier, cancellationToken).ConfigureAwait(false); + + // After applying all of the code fixes, compare the resulting string to the inputted one + await VerifyProjectAsync(newState, project, verifier, cancellationToken).ConfigureAwait(false); + + foreach (var additionalProject in newState.AdditionalProjects) + { + var actualProject = project.Solution.Projects.Single(p => p.Name == additionalProject.Key); + await VerifyProjectAsync(additionalProject.Value, actualProject, verifier, cancellationToken); + } + + // Validate the iteration counts after validating the content + iterationCountFailure?.Throw(); + } + + private async Task VerifyProjectAsync(ProjectState newState, Project project, IVerifier verifier, CancellationToken cancellationToken) + { + // After applying all of the code fixes, compare the resulting string to the inputted one + var updatedDocuments = project.Documents.ToArray(); + + verifier.Equal(newState.Sources.Count, updatedDocuments.Length, $"expected '{nameof(newState)}.{nameof(SolutionState.Sources)}' and '{nameof(updatedDocuments)}' to be equal but '{nameof(newState)}.{nameof(SolutionState.Sources)}' contains '{newState.Sources.Count}' documents and '{nameof(updatedDocuments)}' contains '{updatedDocuments.Length}' documents"); + + for (var i = 0; i < updatedDocuments.Length; i++) + { + var actual = await GetSourceTextFromDocumentAsync(updatedDocuments[i], cancellationToken).ConfigureAwait(false); + verifier.EqualOrDiff(newState.Sources[i].content.ToString(), actual.ToString(), $"content of '{newState.Sources[i].filename}' did not match. Diff shown with expected as baseline:"); + verifier.Equal(newState.Sources[i].content.Encoding, actual.Encoding, $"encoding of '{newState.Sources[i].filename}' was expected to be '{newState.Sources[i].content.Encoding?.WebName}' but was '{actual.Encoding?.WebName}'"); + verifier.Equal(newState.Sources[i].content.ChecksumAlgorithm, actual.ChecksumAlgorithm, $"checksum algorithm of '{newState.Sources[i].filename}' was expected to be '{newState.Sources[i].content.ChecksumAlgorithm}' but was '{actual.ChecksumAlgorithm}'"); + verifier.Equal(newState.Sources[i].filename, updatedDocuments[i].Name, $"file name was expected to be '{newState.Sources[i].filename}' but was '{updatedDocuments[i].Name}'"); + } + + var updatedAdditionalDocuments = project.AdditionalDocuments.ToArray(); + + verifier.Equal(newState.AdditionalFiles.Count, updatedAdditionalDocuments.Length, $"expected '{nameof(newState)}.{nameof(SolutionState.AdditionalFiles)}' and '{nameof(updatedAdditionalDocuments)}' to be equal but '{nameof(newState)}.{nameof(SolutionState.AdditionalFiles)}' contains '{newState.AdditionalFiles.Count}' documents and '{nameof(updatedAdditionalDocuments)}' contains '{updatedAdditionalDocuments.Length}' documents"); + + for (var i = 0; i < updatedAdditionalDocuments.Length; i++) + { + var actual = await updatedAdditionalDocuments[i].GetTextAsync(cancellationToken).ConfigureAwait(false); + verifier.EqualOrDiff(newState.AdditionalFiles[i].content.ToString(), actual.ToString(), $"content of '{newState.AdditionalFiles[i].filename}' did not match. Diff shown with expected as baseline:"); + verifier.Equal(newState.AdditionalFiles[i].content.Encoding, actual.Encoding, $"encoding of '{newState.AdditionalFiles[i].filename}' was expected to be '{newState.AdditionalFiles[i].content.Encoding?.WebName}' but was '{actual.Encoding?.WebName}'"); + verifier.Equal(newState.AdditionalFiles[i].content.ChecksumAlgorithm, actual.ChecksumAlgorithm, $"checksum algorithm of '{newState.AdditionalFiles[i].filename}' was expected to be '{newState.AdditionalFiles[i].content.ChecksumAlgorithm}' but was '{actual.ChecksumAlgorithm}'"); + verifier.Equal(newState.AdditionalFiles[i].filename, updatedAdditionalDocuments[i].Name, $"file name was expected to be '{newState.AdditionalFiles[i].filename}' but was '{updatedAdditionalDocuments[i].Name}'"); + } + + var updatedAnalyzerConfigDocuments = project.AnalyzerConfigDocuments().ToArray(); + + verifier.Equal(newState.AnalyzerConfigFiles.Count, updatedAnalyzerConfigDocuments.Length, $"expected '{nameof(newState)}.{nameof(SolutionState.AnalyzerConfigFiles)}' and '{nameof(updatedAnalyzerConfigDocuments)}' to be equal but '{nameof(newState)}.{nameof(SolutionState.AnalyzerConfigFiles)}' contains '{newState.AnalyzerConfigFiles.Count}' documents and '{nameof(updatedAnalyzerConfigDocuments)}' contains '{updatedAnalyzerConfigDocuments.Length}' documents"); + + for (var i = 0; i < updatedAnalyzerConfigDocuments.Length; i++) + { + var actual = await updatedAnalyzerConfigDocuments[i].GetTextAsync(cancellationToken).ConfigureAwait(false); + verifier.EqualOrDiff(newState.AnalyzerConfigFiles[i].content.ToString(), actual.ToString(), $"content of '{newState.AnalyzerConfigFiles[i].filename}' did not match. Diff shown with expected as baseline:"); + verifier.Equal(newState.AnalyzerConfigFiles[i].content.Encoding, actual.Encoding, $"encoding of '{newState.AnalyzerConfigFiles[i].filename}' was expected to be '{newState.AnalyzerConfigFiles[i].content.Encoding?.WebName}' but was '{actual.Encoding?.WebName}'"); + verifier.Equal(newState.AnalyzerConfigFiles[i].content.ChecksumAlgorithm, actual.ChecksumAlgorithm, $"checksum algorithm of '{newState.AnalyzerConfigFiles[i].filename}' was expected to be '{newState.AnalyzerConfigFiles[i].content.ChecksumAlgorithm}' but was '{actual.ChecksumAlgorithm}'"); + verifier.Equal(newState.AnalyzerConfigFiles[i].filename, updatedAnalyzerConfigDocuments[i].Name, $"file name was expected to be '{newState.AnalyzerConfigFiles[i].filename}' but was '{updatedAnalyzerConfigDocuments[i].Name}'"); + } + } + + private async Task<(Project project, ExceptionDispatchInfo? iterationCountFailure)> FixEachAnalyzerDiagnosticAsync(ImmutableArray analyzers, ImmutableArray codeFixProviders, int? codeFixIndex, string? codeFixEquivalenceKey, Action? codeActionVerifier, Project project, int numberOfIterations, IVerifier verifier, CancellationToken cancellationToken) + { + if (numberOfIterations == -1) + { + // For better error messages, use '==' instead of '<=' for iteration comparison when the right hand + // side is 1. + numberOfIterations = 1; + } + + var expectedNumberOfIterations = numberOfIterations; + if (numberOfIterations < 0) + { + numberOfIterations = -numberOfIterations; + } + + var previousDiagnostics = ImmutableArray.Create<(Project project, Diagnostic diagnostic)>(); + + var currentIteration = -1; + bool done; + do + { + currentIteration++; + + var analyzerDiagnostics = await GetSortedDiagnosticsAsync(project.Solution, analyzers, additionalDiagnostics: ImmutableArray<(Project project, Diagnostic diagnostic)>.Empty, CompilerDiagnostics, verifier, cancellationToken).ConfigureAwait(false); + if (analyzerDiagnostics.Length == 0) + { + break; + } + + if (!AreDiagnosticsDifferent(analyzerDiagnostics, previousDiagnostics)) + { + break; + } + + try + { + verifier.True(--numberOfIterations >= -1, "The upper limit for the number of code fix iterations was exceeded"); + } + catch (Exception ex) + { + return (project, ExceptionDispatchInfo.Capture(ex)); + } + + previousDiagnostics = analyzerDiagnostics; + + var fixableDiagnostics = analyzerDiagnostics + .Where(diagnostic => codeFixProviders.Any(provider => provider.FixableDiagnosticIds.Contains(diagnostic.diagnostic.Id))) + .Where(diagnostic => project.Solution.GetDocument(diagnostic.diagnostic.Location.SourceTree) is object) + .ToImmutableArray(); + + if (CodeFixTestBehaviors.HasFlag(CodeFixTestBehaviors.FixOne)) + { + var diagnosticToFix = TrySelectDiagnosticToFix(fixableDiagnostics.Select(x => x.diagnostic).ToImmutableArray()); + fixableDiagnostics = diagnosticToFix is object ? ImmutableArray.Create(fixableDiagnostics.Single(x => x.diagnostic == diagnosticToFix)) : ImmutableArray<(Project project, Diagnostic diagnostic)>.Empty; + } + + done = true; + var anyActions = false; + foreach (var (_, diagnostic) in fixableDiagnostics) + { + var actions = ImmutableArray.CreateBuilder(); + + var fixableDocument = project.Solution.GetDocument(diagnostic.Location.SourceTree); + foreach (var codeFixProvider in codeFixProviders) + { + if (!codeFixProvider.FixableDiagnosticIds.Contains(diagnostic.Id)) + { + // do not pass unsupported diagnostics to a code fix provider + continue; + } + + var context = new CodeFixContext(fixableDocument, diagnostic, (a, d) => actions.Add(a), cancellationToken); + await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false); + } + + var filteredActions = FilterCodeActions(actions.ToImmutable()); + var actionToApply = TryGetCodeActionToApply(currentIteration, filteredActions, codeFixIndex, codeFixEquivalenceKey, codeActionVerifier, verifier); + if (actionToApply != null) + { + anyActions = true; + + var originalProjectId = project.Id; + var fixedProject = await ApplyCodeActionAsync(fixableDocument.Project, actionToApply, verifier, cancellationToken).ConfigureAwait(false); + if (fixedProject != fixableDocument.Project) + { + done = false; + project = fixedProject.Solution.GetProject(originalProjectId); + break; + } + } + } + + if (!anyActions) + { + verifier.True(done, "Expected to be done executing actions."); + + // Avoid counting iterations that do not provide any code actions + numberOfIterations++; + } + + if (CodeFixTestBehaviors.HasFlag(CodeFixTestBehaviors.FixOne)) + { + break; + } + } + while (!done); + + try + { + if (expectedNumberOfIterations >= 0) + { + verifier.Equal(expectedNumberOfIterations, expectedNumberOfIterations - numberOfIterations, $"Expected '{expectedNumberOfIterations}' iterations but found '{expectedNumberOfIterations - numberOfIterations}' iterations."); + } + else + { + verifier.True(numberOfIterations >= 0, "The upper limit for the number of code fix iterations was exceeded"); + } + } + catch (Exception ex) + { + return (project, ExceptionDispatchInfo.Capture(ex)); + } + + return (project, null); + } + + private Task<(Project project, ExceptionDispatchInfo? iterationCountFailure)> FixAllAnalyzerDiagnosticsInDocumentAsync(ImmutableArray analyzers, ImmutableArray codeFixProviders, int? codeFixIndex, string? codeFixEquivalenceKey, Action? codeActionVerifier, Project project, int numberOfIterations, IVerifier verifier, CancellationToken cancellationToken) + { + return FixAllAnalyerDiagnosticsInScopeAsync(FixAllScope.Document, analyzers, codeFixProviders, codeFixIndex, codeFixEquivalenceKey, codeActionVerifier, project, numberOfIterations, verifier, cancellationToken); + } + + private Task<(Project project, ExceptionDispatchInfo? iterationCountFailure)> FixAllAnalyzerDiagnosticsInProjectAsync(ImmutableArray analyzers, ImmutableArray codeFixProviders, int? codeFixIndex, string? codeFixEquivalenceKey, Action? codeActionVerifier, Project project, int numberOfIterations, IVerifier verifier, CancellationToken cancellationToken) + { + return FixAllAnalyerDiagnosticsInScopeAsync(FixAllScope.Project, analyzers, codeFixProviders, codeFixIndex, codeFixEquivalenceKey, codeActionVerifier, project, numberOfIterations, verifier, cancellationToken); + } + + private Task<(Project project, ExceptionDispatchInfo? iterationCountFailure)> FixAllAnalyzerDiagnosticsInSolutionAsync(ImmutableArray analyzers, ImmutableArray codeFixProviders, int? codeFixIndex, string? codeFixEquivalenceKey, Action? codeActionVerifier, Project project, int numberOfIterations, IVerifier verifier, CancellationToken cancellationToken) + { + return FixAllAnalyerDiagnosticsInScopeAsync(FixAllScope.Solution, analyzers, codeFixProviders, codeFixIndex, codeFixEquivalenceKey, codeActionVerifier, project, numberOfIterations, verifier, cancellationToken); + } + + private async Task<(Project project, ExceptionDispatchInfo? iterationCountFailure)> FixAllAnalyerDiagnosticsInScopeAsync(FixAllScope scope, ImmutableArray analyzers, ImmutableArray codeFixProviders, int? codeFixIndex, string? codeFixEquivalenceKey, Action? codeActionVerifier, Project project, int numberOfIterations, IVerifier verifier, CancellationToken cancellationToken) + { + if (numberOfIterations == -1) + { + // For better error messages, use '==' instead of '<=' for iteration comparison when the right hand + // side is 1. + numberOfIterations = 1; + } + + var expectedNumberOfIterations = numberOfIterations; + if (numberOfIterations < 0) + { + numberOfIterations = -numberOfIterations; + } + + var previousDiagnostics = ImmutableArray.Create<(Project project, Diagnostic diagnostic)>(); + + var currentIteration = -1; + bool done; + do + { + currentIteration++; + + var analyzerDiagnostics = await GetSortedDiagnosticsAsync(project.Solution, analyzers, additionalDiagnostics: ImmutableArray<(Project project, Diagnostic diagnostic)>.Empty, CompilerDiagnostics, verifier, cancellationToken).ConfigureAwait(false); + if (analyzerDiagnostics.Length == 0) + { + break; + } + + if (!AreDiagnosticsDifferent(analyzerDiagnostics, previousDiagnostics)) + { + break; + } + + try + { + verifier.False(--numberOfIterations < -1, "The upper limit for the number of fix all iterations was exceeded"); + } + catch (Exception ex) + { + return (project, ExceptionDispatchInfo.Capture(ex)); + } + + var fixableDiagnostics = analyzerDiagnostics + .Where(diagnostic => codeFixProviders.Any(provider => provider.FixableDiagnosticIds.Contains(diagnostic.diagnostic.Id))) + .Where(diagnostic => project.Solution.GetDocument(diagnostic.diagnostic.Location.SourceTree) is object) + .ToImmutableArray(); + + if (CodeFixTestBehaviors.HasFlag(CodeFixTestBehaviors.FixOne)) + { + var diagnosticToFix = TrySelectDiagnosticToFix(fixableDiagnostics.Select(x => x.diagnostic).ToImmutableArray()); + fixableDiagnostics = diagnosticToFix is object ? ImmutableArray.Create(fixableDiagnostics.Single(x => x.diagnostic == diagnosticToFix)) : ImmutableArray<(Project project, Diagnostic diagnostic)>.Empty; + } + + Diagnostic? firstDiagnostic = null; + CodeFixProvider? effectiveCodeFixProvider = null; + string? equivalenceKey = null; + foreach (var (_, diagnostic) in fixableDiagnostics) + { + var actions = new List<(CodeAction, CodeFixProvider)>(); + + var diagnosticDocument = project.Solution.GetDocument(diagnostic.Location.SourceTree); + foreach (var codeFixProvider in codeFixProviders) + { + if (!codeFixProvider.FixableDiagnosticIds.Contains(diagnostic.Id)) + { + // do not pass unsupported diagnostics to a code fix provider + continue; + } + + var actionsBuilder = ImmutableArray.CreateBuilder(); + var context = new CodeFixContext(diagnosticDocument, diagnostic, (a, d) => actionsBuilder.Add(a), cancellationToken); + await codeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false); + actions.AddRange(FilterCodeActions(actionsBuilder.ToImmutable()).Select(action => (action, codeFixProvider))); + } + + var actionToApply = TryGetCodeActionToApply(currentIteration, actions.Select(a => a.Item1).ToImmutableArray(), codeFixIndex, codeFixEquivalenceKey, codeActionVerifier, verifier); + if (actionToApply != null) + { + firstDiagnostic = diagnostic; + effectiveCodeFixProvider = actions.SingleOrDefault(a => a.Item1 == actionToApply).Item2; + equivalenceKey = actionToApply.EquivalenceKey; + break; + } + } + + var fixAllProvider = effectiveCodeFixProvider?.GetFixAllProvider(); + if (firstDiagnostic == null || fixAllProvider == null) + { + numberOfIterations++; + break; + } + + previousDiagnostics = analyzerDiagnostics; + + done = true; + + FixAllContext.DiagnosticProvider fixAllDiagnosticProvider = TestDiagnosticProvider.Create(analyzerDiagnostics); + + var fixableDocument = project.Solution.GetDocument(firstDiagnostic.Location.SourceTree); + var analyzerDiagnosticIds = analyzers.SelectMany(x => x.SupportedDiagnostics).Select(x => x.Id); + var compilerDiagnosticIds = codeFixProviders.SelectMany(codeFixProvider => codeFixProvider.FixableDiagnosticIds).Where(x => x.StartsWith("CS", StringComparison.Ordinal) || x.StartsWith("BC", StringComparison.Ordinal)); + var disabledDiagnosticIds = project.CompilationOptions.SpecificDiagnosticOptions.Where(x => x.Value == ReportDiagnostic.Suppress).Select(x => x.Key); + var relevantIds = analyzerDiagnosticIds.Concat(compilerDiagnosticIds).Except(disabledDiagnosticIds).Distinct(); + var fixAllContext = new FixAllContext(fixableDocument, effectiveCodeFixProvider, scope, equivalenceKey, relevantIds, fixAllDiagnosticProvider, cancellationToken); + + var action = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(false); + if (action == null) + { + return (project, null); + } + + var originalProjectId = project.Id; + var fixedProject = await ApplyCodeActionAsync(fixableDocument.Project, action, verifier, cancellationToken).ConfigureAwait(false); + if (fixedProject != fixableDocument.Project) + { + done = false; + project = fixedProject.Solution.GetProject(originalProjectId); + } + + if (CodeFixTestBehaviors.HasFlag(CodeFixTestBehaviors.FixOne)) + { + break; + } + } + while (!done); + + try + { + if (expectedNumberOfIterations >= 0) + { + verifier.Equal(expectedNumberOfIterations, expectedNumberOfIterations - numberOfIterations, $"Expected '{expectedNumberOfIterations}' iterations but found '{expectedNumberOfIterations - numberOfIterations}' iterations."); + } + else + { + verifier.True(numberOfIterations >= 0, "The upper limit for the number of code fix iterations was exceeded"); + } + } + catch (Exception ex) + { + return (project, ExceptionDispatchInfo.Capture(ex)); + } + + return (project, null); + } + + /// + /// Get the existing compiler diagnostics on the input document. + /// + /// The to run the compiler diagnostic analyzers on. + /// The that the task will observe. + /// The compiler diagnostics that were found in the code. + private static async Task> GetCompilerDiagnosticsAsync(Project project, CancellationToken cancellationToken) + { + var allDiagnostics = ImmutableArray.Create(); + + foreach (var document in project.Documents) + { + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + allDiagnostics = allDiagnostics.AddRange(semanticModel.GetDiagnostics(cancellationToken: cancellationToken)); + } + + return allDiagnostics; + } + + /// + /// Given a document, turn it into a string based on the syntax root. + /// + /// The to be converted to a string. + /// The that the task will observe. + /// A containing the syntax of the after formatting. + private static async Task GetSourceTextFromDocumentAsync(Document document, CancellationToken cancellationToken) + { + var simplifiedDoc = await Simplifier.ReduceAsync(document, Simplifier.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); + var formatted = await Formatter.FormatAsync(simplifiedDoc, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); + return await formatted.GetTextAsync(cancellationToken).ConfigureAwait(false); + } + + private static bool AreDiagnosticsDifferent(ImmutableArray<(Project project, Diagnostic diagnostic)> analyzerDiagnostics, ImmutableArray<(Project project, Diagnostic diagnostic)> previousDiagnostics) + { + if (analyzerDiagnostics.Length != previousDiagnostics.Length) + { + return true; + } + + for (var i = 0; i < analyzerDiagnostics.Length; i++) + { + if ((analyzerDiagnostics[i].project.Id != previousDiagnostics[i].project.Id) + || (analyzerDiagnostics[i].diagnostic.Id != previousDiagnostics[i].diagnostic.Id) + || (analyzerDiagnostics[i].diagnostic.Location.SourceSpan != previousDiagnostics[i].diagnostic.Location.SourceSpan)) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/CodeFixValidationMode.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/CodeFixValidationMode.cs new file mode 100644 index 000000000..6e825173c --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/CodeFixValidationMode.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + [Obsolete("Use " + nameof(CodeActionValidationMode) + " instead.")] + public enum CodeFixValidationMode + { + /// + None, + + /// + SemanticStructure, + + /// + Full, + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/CodeFixVerifier`4.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/CodeFixVerifier`4.cs new file mode 100644 index 000000000..3d364e28c --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/CodeFixVerifier`4.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// A default verifier for diagnostic analyzers with code fixes. + /// + /// The to test. + /// The to test. + /// The test implementation to use. + /// The type of verifier to use. + public class CodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + where TTest : CodeFixTest, new() + where TVerifier : IVerifier, new() + { + /// + public static DiagnosticResult Diagnostic() + => AnalyzerVerifier.Diagnostic(); + + /// + public static DiagnosticResult Diagnostic(string diagnosticId) + => AnalyzerVerifier.Diagnostic(diagnosticId); + + /// + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => AnalyzerVerifier.Diagnostic(descriptor); + + /// + public static Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + => AnalyzerVerifier.VerifyAnalyzerAsync(source, expected); + + /// + /// Verifies the analyzer provides diagnostics which, in combination with the code fix, produce the expected + /// fixed code. + /// + /// The source text to test. Any diagnostics are defined in markup. + /// The expected fixed source text. Any remaining diagnostics are defined in markup. + /// A representing the asynchronous operation. + public static Task VerifyCodeFixAsync(string source, string fixedSource) + => VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + + /// + /// Verifies the analyzer provides diagnostics which, in combination with the code fix, produce the expected + /// fixed code. + /// + /// The source text to test, which may include markup syntax. + /// The expected diagnostic. This diagnostic is in addition to any diagnostics defined in + /// markup. + /// The expected fixed source text. Any remaining diagnostics are defined in markup. + /// A representing the asynchronous operation. + public static Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource) + => VerifyCodeFixAsync(source, new[] { expected }, fixedSource); + + /// + /// Verifies the analyzer provides diagnostics which, in combination with the code fix, produce the expected + /// fixed code. + /// + /// The source text to test, which may include markup syntax. + /// The expected diagnostics. These diagnostics are in addition to any diagnostics + /// defined in markup. + /// The expected fixed source text. Any remaining diagnostics are defined in markup. + /// A representing the asynchronous operation. + public static Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new TTest + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + return test.RunAsync(CancellationToken.None); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/EmptyCodeFixProvider.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/EmptyCodeFixProvider.cs new file mode 100644 index 000000000..fb0dbb3b4 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/EmptyCodeFixProvider.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Defines a which does not support any diagnostic IDs or register code fixes for any + /// diagnostics. + /// + public sealed class EmptyCodeFixProvider : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds + => ImmutableArray.Empty; + + public override Task RegisterCodeFixesAsync(CodeFixContext context) + => Task.FromResult(true); + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/EnsureNoWorkspacesDesktopReferenceB.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/EnsureNoWorkspacesDesktopReferenceB.cs new file mode 100644 index 000000000..e95baa1ce --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/EnsureNoWorkspacesDesktopReferenceB.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#if DEBUG + +namespace Microsoft.CodeAnalysis +{ + /// + /// This class will fail to compile (CS0419 Ambiguous reference in cref attribute) if the project contains a + /// reference to Microsoft.CodeAnalysis.Workspaces.Desktop. + /// + /// + internal class EnsureNoWorkspacesDesktopReference + { + } +} + +#endif diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/Microsoft.CodeAnalysis.CodeFix.Testing.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/Microsoft.CodeAnalysis.CodeFix.Testing.csproj new file mode 100644 index 000000000..9c27862e5 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/Microsoft.CodeAnalysis.CodeFix.Testing.csproj @@ -0,0 +1,24 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.CodeFix.Testing + Microsoft.CodeAnalysis.Testing + + + + true + Roslyn Code Fix Test Framework Common Types. + Roslyn Code Fix Test Framework Common Types + Roslyn Code Fix Test Framework Common + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..1552a9842 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/PublicAPI.Unshipped.txt @@ -0,0 +1,51 @@ +Microsoft.CodeAnalysis.Testing.CodeFixTest +Microsoft.CodeAnalysis.Testing.CodeFixTest.BatchFixedCode.set -> void +Microsoft.CodeAnalysis.Testing.CodeFixTest.BatchFixedState.get -> Microsoft.CodeAnalysis.Testing.SolutionState +Microsoft.CodeAnalysis.Testing.CodeFixTest.CodeFixEquivalenceKey.get -> string +Microsoft.CodeAnalysis.Testing.CodeFixTest.CodeFixEquivalenceKey.set -> void +Microsoft.CodeAnalysis.Testing.CodeFixTest.CodeFixIndex.get -> int? +Microsoft.CodeAnalysis.Testing.CodeFixTest.CodeFixIndex.set -> void +Microsoft.CodeAnalysis.Testing.CodeFixTest.CodeFixTest() -> void +Microsoft.CodeAnalysis.Testing.CodeFixTest.CodeFixTestBehaviors.get -> Microsoft.CodeAnalysis.Testing.CodeFixTestBehaviors +Microsoft.CodeAnalysis.Testing.CodeFixTest.CodeFixTestBehaviors.set -> void +Microsoft.CodeAnalysis.Testing.CodeFixTest.CodeFixValidationMode.get -> Microsoft.CodeAnalysis.Testing.CodeFixValidationMode +Microsoft.CodeAnalysis.Testing.CodeFixTest.CodeFixValidationMode.set -> void +Microsoft.CodeAnalysis.Testing.CodeFixTest.FixedCode.set -> void +Microsoft.CodeAnalysis.Testing.CodeFixTest.FixedState.get -> Microsoft.CodeAnalysis.Testing.SolutionState +Microsoft.CodeAnalysis.Testing.CodeFixTest.NumberOfFixAllInDocumentIterations.get -> int? +Microsoft.CodeAnalysis.Testing.CodeFixTest.NumberOfFixAllInDocumentIterations.set -> void +Microsoft.CodeAnalysis.Testing.CodeFixTest.NumberOfFixAllInProjectIterations.get -> int? +Microsoft.CodeAnalysis.Testing.CodeFixTest.NumberOfFixAllInProjectIterations.set -> void +Microsoft.CodeAnalysis.Testing.CodeFixTest.NumberOfFixAllIterations.get -> int? +Microsoft.CodeAnalysis.Testing.CodeFixTest.NumberOfFixAllIterations.set -> void +Microsoft.CodeAnalysis.Testing.CodeFixTest.NumberOfIncrementalIterations.get -> int? +Microsoft.CodeAnalysis.Testing.CodeFixTest.NumberOfIncrementalIterations.set -> void +Microsoft.CodeAnalysis.Testing.CodeFixTest.VerifyFixAsync(Microsoft.CodeAnalysis.Testing.SolutionState testState, Microsoft.CodeAnalysis.Testing.SolutionState fixedState, Microsoft.CodeAnalysis.Testing.SolutionState batchFixedState, Microsoft.CodeAnalysis.Testing.IVerifier verifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +Microsoft.CodeAnalysis.Testing.CodeFixTestBehaviors +Microsoft.CodeAnalysis.Testing.CodeFixTestBehaviors.FixOne = 8 -> Microsoft.CodeAnalysis.Testing.CodeFixTestBehaviors +Microsoft.CodeAnalysis.Testing.CodeFixTestBehaviors.None = 0 -> Microsoft.CodeAnalysis.Testing.CodeFixTestBehaviors +Microsoft.CodeAnalysis.Testing.CodeFixTestBehaviors.SkipFixAllCheck = Microsoft.CodeAnalysis.Testing.CodeFixTestBehaviors.SkipFixAllInDocumentCheck | Microsoft.CodeAnalysis.Testing.CodeFixTestBehaviors.SkipFixAllInProjectCheck | Microsoft.CodeAnalysis.Testing.CodeFixTestBehaviors.SkipFixAllInSolutionCheck -> Microsoft.CodeAnalysis.Testing.CodeFixTestBehaviors +Microsoft.CodeAnalysis.Testing.CodeFixTestBehaviors.SkipFixAllInDocumentCheck = 1 -> Microsoft.CodeAnalysis.Testing.CodeFixTestBehaviors +Microsoft.CodeAnalysis.Testing.CodeFixTestBehaviors.SkipFixAllInProjectCheck = 2 -> Microsoft.CodeAnalysis.Testing.CodeFixTestBehaviors +Microsoft.CodeAnalysis.Testing.CodeFixTestBehaviors.SkipFixAllInSolutionCheck = 4 -> Microsoft.CodeAnalysis.Testing.CodeFixTestBehaviors +Microsoft.CodeAnalysis.Testing.CodeFixValidationMode +Microsoft.CodeAnalysis.Testing.CodeFixValidationMode.Full = 2 -> Microsoft.CodeAnalysis.Testing.CodeFixValidationMode +Microsoft.CodeAnalysis.Testing.CodeFixValidationMode.None = 0 -> Microsoft.CodeAnalysis.Testing.CodeFixValidationMode +Microsoft.CodeAnalysis.Testing.CodeFixValidationMode.SemanticStructure = 1 -> Microsoft.CodeAnalysis.Testing.CodeFixValidationMode +Microsoft.CodeAnalysis.Testing.CodeFixVerifier +Microsoft.CodeAnalysis.Testing.CodeFixVerifier.CodeFixVerifier() -> void +Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider +Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider.EmptyCodeFixProvider() -> void +abstract Microsoft.CodeAnalysis.Testing.CodeFixTest.GetCodeFixProviders() -> System.Collections.Generic.IEnumerable +override Microsoft.CodeAnalysis.Testing.CodeFixTest.IsCompilerDiagnosticIncluded(Microsoft.CodeAnalysis.Diagnostic diagnostic, Microsoft.CodeAnalysis.Testing.CompilerDiagnostics compilerDiagnostics) -> bool +override Microsoft.CodeAnalysis.Testing.CodeFixTest.RunImplAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +override Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider.FixableDiagnosticIds.get -> System.Collections.Immutable.ImmutableArray +override Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider.RegisterCodeFixesAsync(Microsoft.CodeAnalysis.CodeFixes.CodeFixContext context) -> System.Threading.Tasks.Task +static Microsoft.CodeAnalysis.Testing.CodeFixVerifier.Diagnostic() -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +static Microsoft.CodeAnalysis.Testing.CodeFixVerifier.Diagnostic(Microsoft.CodeAnalysis.DiagnosticDescriptor descriptor) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +static Microsoft.CodeAnalysis.Testing.CodeFixVerifier.Diagnostic(string diagnosticId) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult +static Microsoft.CodeAnalysis.Testing.CodeFixVerifier.VerifyAnalyzerAsync(string source, params Microsoft.CodeAnalysis.Testing.DiagnosticResult[] expected) -> System.Threading.Tasks.Task +static Microsoft.CodeAnalysis.Testing.CodeFixVerifier.VerifyCodeFixAsync(string source, Microsoft.CodeAnalysis.Testing.DiagnosticResult expected, string fixedSource) -> System.Threading.Tasks.Task +static Microsoft.CodeAnalysis.Testing.CodeFixVerifier.VerifyCodeFixAsync(string source, Microsoft.CodeAnalysis.Testing.DiagnosticResult[] expected, string fixedSource) -> System.Threading.Tasks.Task +static Microsoft.CodeAnalysis.Testing.CodeFixVerifier.VerifyCodeFixAsync(string source, string fixedSource) -> System.Threading.Tasks.Task +virtual Microsoft.CodeAnalysis.Testing.CodeFixTest.TrySelectDiagnosticToFix(System.Collections.Immutable.ImmutableArray fixableDiagnostics) -> Microsoft.CodeAnalysis.Diagnostic diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/TestDiagnosticProvider.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/TestDiagnosticProvider.cs new file mode 100644 index 000000000..07940362d --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeFix.Testing/TestDiagnosticProvider.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; + +namespace Microsoft.CodeAnalysis.Testing +{ + internal sealed class TestDiagnosticProvider : FixAllContext.DiagnosticProvider + { + private readonly ImmutableArray<(Project project, Diagnostic diagnostic)> _diagnostics; + + private TestDiagnosticProvider(ImmutableArray<(Project project, Diagnostic diagnostic)> diagnostics) + { + _diagnostics = diagnostics; + } + + public override Task> GetAllDiagnosticsAsync(Project project, CancellationToken cancellationToken) + => Task.FromResult>(_diagnostics.Where(diagnostic => diagnostic.project.Id == project.Id).Select(diagnostic => diagnostic.diagnostic)); + + public override Task> GetDocumentDiagnosticsAsync(Document document, CancellationToken cancellationToken) + => Task.FromResult(_diagnostics.Where(i => i.diagnostic.Location.GetLineSpan().Path == document.Name).Where(diagnostic => diagnostic.project.Id == document.Project.Id).Select(diagnostic => diagnostic.diagnostic)); + + public override Task> GetProjectDiagnosticsAsync(Project project, CancellationToken cancellationToken) + => Task.FromResult(_diagnostics.Where(i => !i.diagnostic.Location.IsInSource).Where(diagnostic => diagnostic.project.Id == project.Id).Select(diagnostic => diagnostic.diagnostic)); + + internal static TestDiagnosticProvider Create(ImmutableArray<(Project project, Diagnostic diagnostic)> diagnostics) => new TestDiagnosticProvider(diagnostics); + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/CodeRefactoringTest`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/CodeRefactoringTest`1.cs new file mode 100644 index 000000000..4aa1ca2bb --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/CodeRefactoringTest`1.cs @@ -0,0 +1,348 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Runtime.ExceptionServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Testing.Model; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Testing +{ + public abstract class CodeRefactoringTest : CodeActionTest + where TVerifier : IVerifier, new() + { + public static DiagnosticDescriptor TriggerSpanDescriptor { get; } = new DiagnosticDescriptor( + id: "Refactoring", + title: "Refactoring", + messageFormat: string.Empty, + category: "Refactoring", + defaultSeverity: DiagnosticSeverity.Hidden, + isEnabledByDefault: true, + customTags: new[] { WellKnownDiagnosticTags.NotConfigurable }); + + /// + /// Sets the expected output source file for code refactoring testing. + /// + /// + public string FixedCode + { + set + { + if (value != null) + { + FixedState.Sources.Add(value); + } + } + } + + public SolutionState FixedState { get; } + + public bool OffersEmptyRefactoring { get; set; } + + protected CodeRefactoringTest() + { + FixedState = new SolutionState(DefaultTestProjectName, Language, DefaultFilePathPrefix, DefaultFileExt); + } + + protected override IEnumerable GetDiagnosticAnalyzers() + => new DiagnosticAnalyzer[] { new EmptyDiagnosticAnalyzer() }; + + /// + /// Returns the code refactorings being tested - to be implemented in non-abstract class. + /// + /// The to be used. + protected abstract IEnumerable GetCodeRefactoringProviders(); + + protected override async Task RunImplAsync(CancellationToken cancellationToken) + { + Verify.NotEmpty($"{nameof(TestState)}.{nameof(SolutionState.Sources)}", TestState.Sources); + + var analyzers = GetDiagnosticAnalyzers().ToArray(); + var defaultDiagnostic = GetDefaultDiagnostic(analyzers); + var supportedDiagnostics = analyzers.SelectMany(analyzer => analyzer.SupportedDiagnostics).ToImmutableArray(); + var fixableDiagnostics = ImmutableArray.Empty; + + var rawTestState = TestState.WithInheritedValuesApplied(null, fixableDiagnostics); + var rawFixedState = FixedState.WithInheritedValuesApplied(rawTestState, fixableDiagnostics); + + var testState = rawTestState.WithProcessedMarkup(MarkupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, DefaultFilePath); + var fixedState = rawFixedState.WithProcessedMarkup(MarkupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, DefaultFilePath); + + await VerifyDiagnosticsAsync(new EvaluatedProjectState(testState, ReferenceAssemblies), testState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), FilterTriggerSpanResults(testState.ExpectedDiagnostics).ToArray(), Verify.PushContext("Diagnostics of test state"), cancellationToken).ConfigureAwait(false); + + if (CodeActionExpected()) + { + await VerifyDiagnosticsAsync(new EvaluatedProjectState(fixedState, ReferenceAssemblies), fixedState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), FilterTriggerSpanResults(fixedState.ExpectedDiagnostics).ToArray(), Verify.PushContext("Diagnostics of fixed state"), cancellationToken).ConfigureAwait(false); + await VerifyRefactoringAsync(testState, fixedState, GetTriggerSpanResult(testState.ExpectedDiagnostics), Verify, cancellationToken).ConfigureAwait(false); + } + + static IEnumerable FilterTriggerSpanResults(IEnumerable expected) + { + return expected.Where(result => result.Id != TriggerSpanDescriptor.Id); + } + + static DiagnosticResult GetTriggerSpanResult(IEnumerable expected) + { + DiagnosticResult? triggerSpan = null; + foreach (var result in expected) + { + if (result.Id == TriggerSpanDescriptor.Id) + { + Verify.Equal(null, triggerSpan, "Expected the test to only include a single trigger span for refactoring"); + triggerSpan = result; + } + } + + Verify.True(triggerSpan.HasValue, "Expected the test to include a single trigger span for refactoring"); + return triggerSpan!.Value; + } + } + + private bool CodeActionExpected() + { + return CodeActionExpected(FixedState); + } + + protected internal override DiagnosticDescriptor? GetDefaultDiagnostic(DiagnosticAnalyzer[] analyzers) + { + if (base.GetDefaultDiagnostic(analyzers) is { } descriptor) + { + return descriptor; + } + + return TriggerSpanDescriptor; + } + + /// + /// Called to test a C# code refactoring when applied on the input source as a string. + /// + /// The effective input test state. + /// The effective test state after the refactoring is applied. + /// A indicating the location where the refactoring will be triggered. + /// The verifier to use for test assertions. + /// The that the task will observe. + /// A representing the asynchronous operation. + protected async Task VerifyRefactoringAsync(SolutionState testState, SolutionState fixedState, DiagnosticResult triggerSpan, IVerifier verifier, CancellationToken cancellationToken) + { + var numberOfIncrementalIterations = OffersEmptyRefactoring || HasAnyChange(testState, fixedState, recursive: true) ? 1 : 0; + await VerifyRefactoringAsync(Language, triggerSpan, GetCodeRefactoringProviders().ToImmutableArray(), testState, fixedState, numberOfIncrementalIterations, ApplyRefactoringAsync, verifier.PushContext("Code refactoring application"), cancellationToken); + } + + private async Task VerifyRefactoringAsync( + string language, + DiagnosticResult triggerSpan, + ImmutableArray codeRefactoringProviders, + SolutionState oldState, + SolutionState newState, + int numberOfIterations, + Func, int?, string?, Action?, Project, int, IVerifier, CancellationToken, Task<(Project project, ExceptionDispatchInfo? iterationCountFailure)>> getFixedProject, + IVerifier verifier, + CancellationToken cancellationToken) + { + var project = await CreateProjectAsync(new EvaluatedProjectState(oldState, ReferenceAssemblies), oldState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), cancellationToken); + _ = await GetCompilerDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); + + ExceptionDispatchInfo? iterationCountFailure; + (project, iterationCountFailure) = await getFixedProject(triggerSpan, codeRefactoringProviders, CodeActionIndex, CodeActionEquivalenceKey, CodeActionVerifier, project, numberOfIterations, verifier, cancellationToken).ConfigureAwait(false); + + // After applying the refactoring, compare the resulting string to the inputted one + await VerifyProjectAsync(newState, project, verifier, cancellationToken).ConfigureAwait(false); + + foreach (var additionalProject in newState.AdditionalProjects) + { + var actualProject = project.Solution.Projects.Single(p => p.Name == additionalProject.Key); + await VerifyProjectAsync(additionalProject.Value, actualProject, verifier, cancellationToken); + } + + // Validate the iteration counts after validating the content + iterationCountFailure?.Throw(); + } + + private async Task VerifyProjectAsync(ProjectState newState, Project project, IVerifier verifier, CancellationToken cancellationToken) + { + // After applying the refactoring, compare the resulting string to the inputted one + var updatedDocuments = project.Documents.ToArray(); + + verifier.Equal(newState.Sources.Count, updatedDocuments.Length, $"expected '{nameof(newState)}.{nameof(SolutionState.Sources)}' and '{nameof(updatedDocuments)}' to be equal but '{nameof(newState)}.{nameof(SolutionState.Sources)}' contains '{newState.Sources.Count}' documents and '{nameof(updatedDocuments)}' contains '{updatedDocuments.Length}' documents"); + + for (var i = 0; i < updatedDocuments.Length; i++) + { + var actual = await GetSourceTextFromDocumentAsync(updatedDocuments[i], cancellationToken).ConfigureAwait(false); + verifier.EqualOrDiff(newState.Sources[i].content.ToString(), actual.ToString(), $"content of '{newState.Sources[i].filename}' did not match. Diff shown with expected as baseline:"); + verifier.Equal(newState.Sources[i].content.Encoding, actual.Encoding, $"encoding of '{newState.Sources[i].filename}' was expected to be '{newState.Sources[i].content.Encoding?.WebName}' but was '{actual.Encoding?.WebName}'"); + verifier.Equal(newState.Sources[i].content.ChecksumAlgorithm, actual.ChecksumAlgorithm, $"checksum algorithm of '{newState.Sources[i].filename}' was expected to be '{newState.Sources[i].content.ChecksumAlgorithm}' but was '{actual.ChecksumAlgorithm}'"); + verifier.Equal(newState.Sources[i].filename, updatedDocuments[i].Name, $"file name was expected to be '{newState.Sources[i].filename}' but was '{updatedDocuments[i].Name}'"); + } + + var updatedAdditionalDocuments = project.AdditionalDocuments.ToArray(); + + verifier.Equal(newState.AdditionalFiles.Count, updatedAdditionalDocuments.Length, $"expected '{nameof(newState)}.{nameof(SolutionState.AdditionalFiles)}' and '{nameof(updatedAdditionalDocuments)}' to be equal but '{nameof(newState)}.{nameof(SolutionState.AdditionalFiles)}' contains '{newState.AdditionalFiles.Count}' documents and '{nameof(updatedAdditionalDocuments)}' contains '{updatedAdditionalDocuments.Length}' documents"); + + for (var i = 0; i < updatedAdditionalDocuments.Length; i++) + { + var actual = await updatedAdditionalDocuments[i].GetTextAsync(cancellationToken).ConfigureAwait(false); + verifier.EqualOrDiff(newState.AdditionalFiles[i].content.ToString(), actual.ToString(), $"content of '{newState.AdditionalFiles[i].filename}' did not match. Diff shown with expected as baseline:"); + verifier.Equal(newState.AdditionalFiles[i].content.Encoding, actual.Encoding, $"encoding of '{newState.AdditionalFiles[i].filename}' was expected to be '{newState.AdditionalFiles[i].content.Encoding?.WebName}' but was '{actual.Encoding?.WebName}'"); + verifier.Equal(newState.AdditionalFiles[i].content.ChecksumAlgorithm, actual.ChecksumAlgorithm, $"checksum algorithm of '{newState.AdditionalFiles[i].filename}' was expected to be '{newState.AdditionalFiles[i].content.ChecksumAlgorithm}' but was '{actual.ChecksumAlgorithm}'"); + verifier.Equal(newState.AdditionalFiles[i].filename, updatedAdditionalDocuments[i].Name, $"file name was expected to be '{newState.AdditionalFiles[i].filename}' but was '{updatedAdditionalDocuments[i].Name}'"); + } + + var updatedAnalyzerConfigDocuments = project.AnalyzerConfigDocuments().ToArray(); + + verifier.Equal(newState.AnalyzerConfigFiles.Count, updatedAnalyzerConfigDocuments.Length, $"expected '{nameof(newState)}.{nameof(SolutionState.AnalyzerConfigFiles)}' and '{nameof(updatedAnalyzerConfigDocuments)}' to be equal but '{nameof(newState)}.{nameof(SolutionState.AnalyzerConfigFiles)}' contains '{newState.AnalyzerConfigFiles.Count}' documents and '{nameof(updatedAnalyzerConfigDocuments)}' contains '{updatedAnalyzerConfigDocuments.Length}' documents"); + + for (var i = 0; i < updatedAnalyzerConfigDocuments.Length; i++) + { + var actual = await updatedAnalyzerConfigDocuments[i].GetTextAsync(cancellationToken).ConfigureAwait(false); + verifier.EqualOrDiff(newState.AnalyzerConfigFiles[i].content.ToString(), actual.ToString(), $"content of '{newState.AnalyzerConfigFiles[i].filename}' did not match. Diff shown with expected as baseline:"); + verifier.Equal(newState.AnalyzerConfigFiles[i].content.Encoding, actual.Encoding, $"encoding of '{newState.AnalyzerConfigFiles[i].filename}' was expected to be '{newState.AnalyzerConfigFiles[i].content.Encoding?.WebName}' but was '{actual.Encoding?.WebName}'"); + verifier.Equal(newState.AnalyzerConfigFiles[i].content.ChecksumAlgorithm, actual.ChecksumAlgorithm, $"checksum algorithm of '{newState.AnalyzerConfigFiles[i].filename}' was expected to be '{newState.AnalyzerConfigFiles[i].content.ChecksumAlgorithm}' but was '{actual.ChecksumAlgorithm}'"); + verifier.Equal(newState.AnalyzerConfigFiles[i].filename, updatedAnalyzerConfigDocuments[i].Name, $"file name was expected to be '{newState.AnalyzerConfigFiles[i].filename}' but was '{updatedAnalyzerConfigDocuments[i].Name}'"); + } + } + + private async Task<(Project project, ExceptionDispatchInfo? iterationCountFailure)> ApplyRefactoringAsync(DiagnosticResult triggerSpan, ImmutableArray codeRefactoringProviders, int? codeActionIndex, string? codeActionEquivalenceKey, Action? codeActionVerifier, Project project, int numberOfIterations, IVerifier verifier, CancellationToken cancellationToken) + { + if (numberOfIterations == -1) + { + // For better error messages, use '==' instead of '<=' for iteration comparison when the right hand + // side is 1. + numberOfIterations = 1; + } + + var expectedNumberOfIterations = numberOfIterations; + if (numberOfIterations < 0) + { + numberOfIterations = -numberOfIterations; + } + + var currentIteration = -1; + bool done; + do + { + currentIteration++; + + try + { + verifier.True(--numberOfIterations >= -1, "The upper limit for the number of code fix iterations was exceeded"); + } + catch (Exception ex) + { + return (project, ExceptionDispatchInfo.Capture(ex)); + } + + done = true; + var anyActions = false; + var actions = ImmutableArray.CreateBuilder(); + + var location = await GetTriggerLocationAsync(); + var triggerDocument = project.Solution.GetDocument(location.SourceTree); + + foreach (var codeRefactoringProvider in codeRefactoringProviders) + { + var context = new CodeRefactoringContext(triggerDocument, location.SourceSpan, actions.Add, cancellationToken); + await codeRefactoringProvider.ComputeRefactoringsAsync(context).ConfigureAwait(false); + } + + var filteredActions = FilterCodeActions(actions.ToImmutable()); + var actionToApply = TryGetCodeActionToApply(currentIteration, filteredActions, codeActionIndex, codeActionEquivalenceKey, codeActionVerifier, verifier); + if (actionToApply != null) + { + anyActions = true; + + var originalProjectId = project.Id; + var fixedProject = await ApplyCodeActionAsync(triggerDocument.Project, actionToApply, verifier, cancellationToken).ConfigureAwait(false); + if (fixedProject != triggerDocument.Project) + { + done = false; + project = fixedProject.Solution.GetProject(originalProjectId); + break; + } + } + + if (!anyActions) + { + verifier.True(done, "Expected to be done executing actions."); + + // Avoid counting iterations that do not provide any code actions + numberOfIterations++; + } + } + while (!done); + + try + { + if (expectedNumberOfIterations >= 0) + { + verifier.Equal(expectedNumberOfIterations, expectedNumberOfIterations - numberOfIterations, $"Expected '{expectedNumberOfIterations}' iterations but found '{expectedNumberOfIterations - numberOfIterations}' iterations."); + } + else + { + verifier.True(numberOfIterations >= 0, "The upper limit for the number of code fix iterations was exceeded"); + } + } + catch (Exception ex) + { + return (project, ExceptionDispatchInfo.Capture(ex)); + } + + return (project, null); + + async Task GetTriggerLocationAsync() + { + var path = triggerSpan.Spans[0].Span.Path; + var span = triggerSpan.Spans[0].Span.Span; + + var documentIds = project.Solution.GetDocumentIdsWithFilePath(triggerSpan.Spans[0].Span.Path); + var document = project.Solution.GetDocument(documentIds.Single()); + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + + return Location.Create(tree, text.Lines.GetTextSpan(span)); + } + } + + /// + /// Get the existing compiler diagnostics on the input document. + /// + /// The to run the compiler diagnostic analyzers on. + /// The that the task will observe. + /// The compiler diagnostics that were found in the code. + private static async Task> GetCompilerDiagnosticsAsync(Project project, CancellationToken cancellationToken) + { + var allDiagnostics = ImmutableArray.Create(); + + foreach (var document in project.Documents) + { + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + allDiagnostics = allDiagnostics.AddRange(semanticModel.GetDiagnostics(cancellationToken: cancellationToken)); + } + + return allDiagnostics; + } + + /// + /// Given a document, turn it into a string based on the syntax root. + /// + /// The to be converted to a string. + /// The that the task will observe. + /// A containing the syntax of the after formatting. + private static async Task GetSourceTextFromDocumentAsync(Document document, CancellationToken cancellationToken) + { + var simplifiedDoc = await Simplifier.ReduceAsync(document, Simplifier.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); + var formatted = await Formatter.FormatAsync(simplifiedDoc, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); + return await formatted.GetTextAsync(cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/CodeRefactoringVerifier`3.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/CodeRefactoringVerifier`3.cs new file mode 100644 index 000000000..5e7e6dd47 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/CodeRefactoringVerifier`3.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeRefactorings; + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// A default verifier for code refactorings. + /// + /// The to test. + /// The test implementation to use. + /// The type of verifier to use. + public class CodeRefactoringVerifier + where TCodeRefactoring : CodeRefactoringProvider, new() + where TTest : CodeRefactoringTest, new() + where TVerifier : IVerifier, new() + { + /// + /// Verifies the application of the code refactoring produces the expected result. + /// + /// The source text to test. Any diagnostics are defined in markup. + /// The expected fixed source text. Any remaining diagnostics are defined in markup. + /// A representing the asynchronous operation. + public static Task VerifyRefactoringAsync(string source, string fixedSource) + => VerifyRefactoringAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + + /// + /// Verifies the application of the code refactoring produces the expected result. + /// + /// The source text to test, which may include markup syntax. + /// The expected diagnostic. This diagnostic is in addition to any diagnostics defined in + /// markup. + /// The expected fixed source text. Any remaining diagnostics are defined in markup. + /// A representing the asynchronous operation. + public static Task VerifyRefactoringAsync(string source, DiagnosticResult expected, string fixedSource) + => VerifyRefactoringAsync(source, new[] { expected }, fixedSource); + + /// + /// Verifies the application of the code refactoring produces the expected result. + /// + /// The source text to test, which may include markup syntax. + /// The expected diagnostics. These diagnostics are in addition to any diagnostics + /// defined in markup. + /// The expected fixed source text. Any remaining diagnostics are defined in markup. + /// A representing the asynchronous operation. + public static Task VerifyRefactoringAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new TTest + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + return test.RunAsync(CancellationToken.None); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/EmptyCodeRefactoringProvider.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/EmptyCodeRefactoringProvider.cs new file mode 100644 index 000000000..a3b8ab079 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/EmptyCodeRefactoringProvider.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeRefactorings; + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Defines a which does not register code refactorings for any source spans. + /// + public sealed class EmptyCodeRefactoringProvider : CodeRefactoringProvider + { + public override Task ComputeRefactoringsAsync(CodeRefactoringContext context) + => Task.FromResult(true); + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/EnsureNoWorkspacesDesktopReferenceB.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/EnsureNoWorkspacesDesktopReferenceB.cs new file mode 100644 index 000000000..e95baa1ce --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/EnsureNoWorkspacesDesktopReferenceB.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#if DEBUG + +namespace Microsoft.CodeAnalysis +{ + /// + /// This class will fail to compile (CS0419 Ambiguous reference in cref attribute) if the project contains a + /// reference to Microsoft.CodeAnalysis.Workspaces.Desktop. + /// + /// + internal class EnsureNoWorkspacesDesktopReference + { + } +} + +#endif diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing.csproj new file mode 100644 index 000000000..8950f30cf --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing.csproj @@ -0,0 +1,23 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.Testing + + + + true + Roslyn Code Refactoring Test Framework Common Types. + Roslyn Code Refactoring Test Framework Common Types + Roslyn Code Refactoring Test Framework Common + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..f2aaec429 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.CodeRefactoring.Testing/PublicAPI.Unshipped.txt @@ -0,0 +1,20 @@ +Microsoft.CodeAnalysis.Testing.CodeRefactoringTest +Microsoft.CodeAnalysis.Testing.CodeRefactoringTest.CodeRefactoringTest() -> void +Microsoft.CodeAnalysis.Testing.CodeRefactoringTest.FixedCode.set -> void +Microsoft.CodeAnalysis.Testing.CodeRefactoringTest.FixedState.get -> Microsoft.CodeAnalysis.Testing.SolutionState +Microsoft.CodeAnalysis.Testing.CodeRefactoringTest.OffersEmptyRefactoring.get -> bool +Microsoft.CodeAnalysis.Testing.CodeRefactoringTest.OffersEmptyRefactoring.set -> void +Microsoft.CodeAnalysis.Testing.CodeRefactoringTest.VerifyRefactoringAsync(Microsoft.CodeAnalysis.Testing.SolutionState testState, Microsoft.CodeAnalysis.Testing.SolutionState fixedState, Microsoft.CodeAnalysis.Testing.DiagnosticResult triggerSpan, Microsoft.CodeAnalysis.Testing.IVerifier verifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +Microsoft.CodeAnalysis.Testing.CodeRefactoringVerifier +Microsoft.CodeAnalysis.Testing.CodeRefactoringVerifier.CodeRefactoringVerifier() -> void +Microsoft.CodeAnalysis.Testing.EmptyCodeRefactoringProvider +Microsoft.CodeAnalysis.Testing.EmptyCodeRefactoringProvider.EmptyCodeRefactoringProvider() -> void +abstract Microsoft.CodeAnalysis.Testing.CodeRefactoringTest.GetCodeRefactoringProviders() -> System.Collections.Generic.IEnumerable +override Microsoft.CodeAnalysis.Testing.CodeRefactoringTest.GetDefaultDiagnostic(Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer[] analyzers) -> Microsoft.CodeAnalysis.DiagnosticDescriptor +override Microsoft.CodeAnalysis.Testing.CodeRefactoringTest.GetDiagnosticAnalyzers() -> System.Collections.Generic.IEnumerable +override Microsoft.CodeAnalysis.Testing.CodeRefactoringTest.RunImplAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +override Microsoft.CodeAnalysis.Testing.EmptyCodeRefactoringProvider.ComputeRefactoringsAsync(Microsoft.CodeAnalysis.CodeRefactorings.CodeRefactoringContext context) -> System.Threading.Tasks.Task +static Microsoft.CodeAnalysis.Testing.CodeRefactoringTest.TriggerSpanDescriptor.get -> Microsoft.CodeAnalysis.DiagnosticDescriptor +static Microsoft.CodeAnalysis.Testing.CodeRefactoringVerifier.VerifyRefactoringAsync(string source, Microsoft.CodeAnalysis.Testing.DiagnosticResult expected, string fixedSource) -> System.Threading.Tasks.Task +static Microsoft.CodeAnalysis.Testing.CodeRefactoringVerifier.VerifyRefactoringAsync(string source, Microsoft.CodeAnalysis.Testing.DiagnosticResult[] expected, string fixedSource) -> System.Threading.Tasks.Task +static Microsoft.CodeAnalysis.Testing.CodeRefactoringVerifier.VerifyRefactoringAsync(string source, string fixedSource) -> System.Threading.Tasks.Task diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/EmptySourceGeneratorProvider.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/EmptySourceGeneratorProvider.cs new file mode 100644 index 000000000..3d34ec6a1 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/EmptySourceGeneratorProvider.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// Defines a which does not add any sources. + /// + public sealed class EmptySourceGeneratorProvider : ISourceGenerator + { + public void Execute(GeneratorExecutionContext context) + { + } + + public void Initialize(GeneratorInitializationContext context) + { + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing.csproj new file mode 100644 index 000000000..42ecea0f4 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing.csproj @@ -0,0 +1,23 @@ + + + + $(TestingLibrarySourceGeneratorTargetFrameworks) + Microsoft.CodeAnalysis.Testing + + + + true + Roslyn Source Generator Test Framework Common Types. + Roslyn Source Generator Test Framework Common Types + Roslyn Source Generator Test Framework Common + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..09cd1e21f --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/PublicAPI.Unshipped.txt @@ -0,0 +1,14 @@ +Microsoft.CodeAnalysis.Testing.EmptySourceGeneratorProvider +Microsoft.CodeAnalysis.Testing.EmptySourceGeneratorProvider.EmptySourceGeneratorProvider() -> void +Microsoft.CodeAnalysis.Testing.EmptySourceGeneratorProvider.Execute(Microsoft.CodeAnalysis.GeneratorExecutionContext context) -> void +Microsoft.CodeAnalysis.Testing.EmptySourceGeneratorProvider.Initialize(Microsoft.CodeAnalysis.GeneratorInitializationContext context) -> void +Microsoft.CodeAnalysis.Testing.SourceGeneratorTest +Microsoft.CodeAnalysis.Testing.SourceGeneratorTest.SourceGeneratorTest() -> void +Microsoft.CodeAnalysis.Testing.SourceGeneratorTest.VerifySourceGeneratorAsync(Microsoft.CodeAnalysis.Testing.SolutionState testState, Microsoft.CodeAnalysis.Testing.IVerifier verifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +Microsoft.CodeAnalysis.Testing.SourceGeneratorVerifier +Microsoft.CodeAnalysis.Testing.SourceGeneratorVerifier.SourceGeneratorVerifier() -> void +abstract Microsoft.CodeAnalysis.Testing.SourceGeneratorTest.CreateGeneratorDriver(Microsoft.CodeAnalysis.Project project, System.Collections.Immutable.ImmutableArray sourceGenerators) -> Microsoft.CodeAnalysis.GeneratorDriver +abstract Microsoft.CodeAnalysis.Testing.SourceGeneratorTest.GetSourceGenerators() -> System.Collections.Generic.IEnumerable +override Microsoft.CodeAnalysis.Testing.SourceGeneratorTest.GetDiagnosticAnalyzers() -> System.Collections.Generic.IEnumerable +override Microsoft.CodeAnalysis.Testing.SourceGeneratorTest.GetProjectCompilationAsync(Microsoft.CodeAnalysis.Project project, Microsoft.CodeAnalysis.Testing.IVerifier verifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +override Microsoft.CodeAnalysis.Testing.SourceGeneratorTest.RunImplAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs new file mode 100644 index 000000000..394b9bae3 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorTest`1.cs @@ -0,0 +1,153 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Runtime.ExceptionServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Testing.Model; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Testing +{ + public abstract class SourceGeneratorTest : AnalyzerTest + where TVerifier : IVerifier, new() + { + protected override IEnumerable GetDiagnosticAnalyzers() + => Enumerable.Empty(); + + /// + /// Returns the source generators being tested - to be implemented in non-abstract class. + /// + /// The to be used. + protected abstract IEnumerable GetSourceGenerators(); + + protected abstract GeneratorDriver CreateGeneratorDriver(Project project, ImmutableArray sourceGenerators); + + protected override async Task RunImplAsync(CancellationToken cancellationToken) + { + var analyzers = GetDiagnosticAnalyzers().ToArray(); + var defaultDiagnostic = GetDefaultDiagnostic(analyzers); + var supportedDiagnostics = analyzers.SelectMany(analyzer => analyzer.SupportedDiagnostics).ToImmutableArray(); + var fixableDiagnostics = ImmutableArray.Empty; + var testState = TestState.WithInheritedValuesApplied(null, fixableDiagnostics).WithProcessedMarkup(MarkupOptions, defaultDiagnostic, supportedDiagnostics, fixableDiagnostics, DefaultFilePath); + + var diagnostics = await VerifySourceGeneratorAsync(testState, Verify, cancellationToken).ConfigureAwait(false); + await VerifyDiagnosticsAsync(new EvaluatedProjectState(testState, ReferenceAssemblies).WithAdditionalDiagnostics(diagnostics), testState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), testState.ExpectedDiagnostics.ToArray(), Verify.PushContext("Diagnostics of test state"), cancellationToken).ConfigureAwait(false); + } + + protected override async Task GetProjectCompilationAsync(Project project, IVerifier verifier, CancellationToken cancellationToken) + { + var (finalProject, diagnostics) = await ApplySourceGeneratorAsync(GetSourceGenerators().ToImmutableArray(), project, verifier, cancellationToken).ConfigureAwait(false); + return (await finalProject.GetCompilationAsync(cancellationToken).ConfigureAwait(false))!; + } + + /// + /// Called to test a C# source generator when applied on the input source as a string. + /// + /// The effective input test state. + /// The verifier to use for test assertions. + /// The that the task will observe. + /// A representing the asynchronous operation. + protected async Task> VerifySourceGeneratorAsync(SolutionState testState, IVerifier verifier, CancellationToken cancellationToken) + { + return await VerifySourceGeneratorAsync(Language, GetSourceGenerators().ToImmutableArray(), testState, ApplySourceGeneratorAsync, verifier.PushContext("Source generator application"), cancellationToken); + } + + private async Task> VerifySourceGeneratorAsync( + string language, + ImmutableArray sourceGenerators, + SolutionState testState, + Func, Project, IVerifier, CancellationToken, Task<(Project project, ImmutableArray diagnostics)>> getFixedProject, + IVerifier verifier, + CancellationToken cancellationToken) + { + var project = await CreateProjectAsync(new EvaluatedProjectState(testState, ReferenceAssemblies), testState.AdditionalProjects.Values.Select(additionalProject => new EvaluatedProjectState(additionalProject, ReferenceAssemblies)).ToImmutableArray(), cancellationToken); + _ = await GetCompilerDiagnosticsAsync(project, verifier, cancellationToken).ConfigureAwait(false); + + ImmutableArray diagnostics; + (project, diagnostics) = await getFixedProject(sourceGenerators, project, verifier, cancellationToken).ConfigureAwait(false); + + // After applying the source generator, compare the resulting string to the inputted one + if (!TestBehaviors.HasFlag(TestBehaviors.SkipGeneratedSourcesCheck)) + { + var updatedDocuments = project.Documents.ToArray(); + var expectedSources = testState.Sources.Concat(testState.GeneratedSources).ToArray(); + + verifier.Equal(expectedSources.Length, updatedDocuments.Length, $"expected '{nameof(testState)}.{nameof(SolutionState.Sources)}' with '{nameof(testState)}.{nameof(SolutionState.GeneratedSources)}' to match '{nameof(updatedDocuments)}', but '{nameof(testState)}.{nameof(SolutionState.Sources)}' with '{nameof(testState)}.{nameof(SolutionState.GeneratedSources)}' contains '{expectedSources.Length}' documents and '{nameof(updatedDocuments)}' contains '{updatedDocuments.Length}' documents"); + + for (var i = 0; i < updatedDocuments.Length; i++) + { + var actual = await GetSourceTextFromDocumentAsync(updatedDocuments[i], cancellationToken).ConfigureAwait(false); + verifier.EqualOrDiff(expectedSources[i].content.ToString(), actual.ToString(), $"content of '{expectedSources[i].filename}' did not match. Diff shown with expected as baseline:"); + verifier.Equal(expectedSources[i].content.Encoding, actual.Encoding, $"encoding of '{expectedSources[i].filename}' was expected to be '{expectedSources[i].content.Encoding?.WebName}' but was '{actual.Encoding?.WebName}'"); + verifier.Equal(expectedSources[i].content.ChecksumAlgorithm, actual.ChecksumAlgorithm, $"checksum algorithm of '{expectedSources[i].filename}' was expected to be '{expectedSources[i].content.ChecksumAlgorithm}' but was '{actual.ChecksumAlgorithm}'"); + verifier.Equal(expectedSources[i].filename, updatedDocuments[i].Name, $"file name was expected to be '{expectedSources[i].filename}' but was '{updatedDocuments[i].Name}'"); + } + } + + return diagnostics; + } + + private async Task<(Project project, ImmutableArray diagnostics)> ApplySourceGeneratorAsync(ImmutableArray sourceGenerators, Project project, IVerifier verifier, CancellationToken cancellationToken) + { + var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + verifier.True(compilation is { }); + + var driver = CreateGeneratorDriver(project, sourceGenerators).RunGenerators(compilation, cancellationToken); + var result = driver.GetRunResult(); + + var updatedProject = project; + foreach (var tree in result.GeneratedTrees) + { + updatedProject = updatedProject.AddDocument(tree.FilePath, await tree.GetTextAsync(cancellationToken).ConfigureAwait(false), filePath: tree.FilePath).Project; + } + + return (updatedProject, result.Diagnostics); + } + + /// + /// Get the existing compiler diagnostics on the input document. + /// + /// The to run the compiler diagnostic analyzers on. + /// The verifier to use for test assertions. + /// The that the task will observe. + /// The compiler diagnostics that were found in the code. + private static async Task> GetCompilerDiagnosticsAsync(Project project, IVerifier verifier, CancellationToken cancellationToken) + { + var allDiagnostics = ImmutableArray.Create(); + + foreach (var document in project.Documents) + { + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + verifier.True(semanticModel is { }); + + allDiagnostics = allDiagnostics.AddRange(semanticModel.GetDiagnostics(cancellationToken: cancellationToken)); + } + + return allDiagnostics; + } + + /// + /// Given a document, turn it into a string based on the syntax root. + /// + /// The to be converted to a string. + /// The that the task will observe. + /// A containing the syntax of the after formatting. + private static async Task GetSourceTextFromDocumentAsync(Document document, CancellationToken cancellationToken) + { + var simplifiedDoc = await Simplifier.ReduceAsync(document, Simplifier.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); + var formatted = await Formatter.FormatAsync(simplifiedDoc, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false); + return await formatted.GetTextAsync(cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorVerifier`3.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorVerifier`3.cs new file mode 100644 index 000000000..740172ccd --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.SourceGenerators.Testing/SourceGeneratorVerifier`3.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.Testing +{ + /// + /// A default verifier for source generators. + /// + /// The to test. + /// The test implementation to use. + /// The type of verifier to use. + public class SourceGeneratorVerifier + where TSourceGenerator : ISourceGenerator, new() + where TTest : SourceGeneratorTest, new() + where TVerifier : IVerifier, new() + { + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.MSTest/MSTestVerifier.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.MSTest/MSTestVerifier.cs new file mode 100644 index 000000000..3cd79b1e2 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.MSTest/MSTestVerifier.cs @@ -0,0 +1,156 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.CodeAnalysis.Testing.Verifiers +{ + public class MSTestVerifier : IVerifier + { + public MSTestVerifier() + : this(ImmutableStack.Empty) + { + } + + protected MSTestVerifier(ImmutableStack context) + { + Context = context ?? throw new ArgumentNullException(nameof(context)); + } + + protected ImmutableStack Context { get; } + + public virtual void Empty(string collectionName, IEnumerable collection) + { + Assert.IsFalse(collection?.Any() == true, CreateMessage($"expected '{collectionName}' to be empty, contains '{collection?.Count()}' elements")); + } + + public virtual void Equal(T expected, T actual, string? message = null) + { + if (message is null && Context.IsEmpty) + { + Assert.AreEqual(expected, actual); + } + else + { + Assert.AreEqual(expected, actual, CreateMessage(message)); + } + } + + public virtual void True([DoesNotReturnIf(false)] bool assert, string? message = null) + { + if (message is null && Context.IsEmpty) + { + Assert.IsTrue(assert); + } + else + { + Assert.IsTrue(assert, CreateMessage(message)); + } + } + + public virtual void False([DoesNotReturnIf(true)] bool assert, string? message = null) + { + if (message is null && Context.IsEmpty) + { + Assert.IsFalse(assert); + } + else + { + Assert.IsFalse(assert, CreateMessage(message)); + } + } + + [DoesNotReturn] + public virtual void Fail(string? message = null) + { + if (message is null && Context.IsEmpty) + { + Assert.Fail(); + } + else + { + Assert.Fail(CreateMessage(message)); + } + + throw ExceptionUtilities.Unreachable; + } + + public virtual void LanguageIsSupported(string language) + { + Assert.IsFalse(language != LanguageNames.CSharp && language != LanguageNames.VisualBasic, CreateMessage($"Unsupported Language: '{language}'")); + } + + public virtual void NotEmpty(string collectionName, IEnumerable collection) + { + Assert.IsTrue(collection?.Any() == true, CreateMessage($"expected '{collectionName}' to be non-empty, contains")); + } + + public virtual void SequenceEqual(IEnumerable expected, IEnumerable actual, IEqualityComparer? equalityComparer = null, string? message = null) + { + var comparer = new SequenceEqualEnumerableEqualityComparer(equalityComparer); + var areEqual = comparer.Equals(expected, actual); + if (!areEqual) + { + Assert.Fail(CreateMessage(message)); + } + } + + public virtual IVerifier PushContext(string context) + { + Assert.AreEqual(typeof(MSTestVerifier), GetType()); + return new MSTestVerifier(Context.Push(context)); + } + + protected virtual string CreateMessage(string? message) + { + foreach (var frame in Context) + { + message = "Context: " + frame + Environment.NewLine + message; + } + + return message ?? string.Empty; + } + + private sealed class SequenceEqualEnumerableEqualityComparer : IEqualityComparer?> + { + private readonly IEqualityComparer _itemEqualityComparer; + + public SequenceEqualEnumerableEqualityComparer(IEqualityComparer? itemEqualityComparer) + { + _itemEqualityComparer = itemEqualityComparer ?? EqualityComparer.Default; + } + + public bool Equals(IEnumerable? x, IEnumerable? y) + { + if (ReferenceEquals(x, y)) { return true; } + if (x is null || y is null) { return false; } + + return x.SequenceEqual(y, _itemEqualityComparer); + } + + public int GetHashCode(IEnumerable? obj) + { + if (obj is null) + { + return 0; + } + + // From System.Tuple + // + // The suppression is required due to an invalid contract in IEqualityComparer + // https://github.com/dotnet/runtime/issues/30998 + return obj + .Select(item => _itemEqualityComparer.GetHashCode(item!)) + .Aggregate( + 0, + (aggHash, nextHash) => ((aggHash << 5) + aggHash) ^ nextHash); + } + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.MSTest/Microsoft.CodeAnalysis.Testing.Verifiers.MSTest.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.MSTest/Microsoft.CodeAnalysis.Testing.Verifiers.MSTest.csproj new file mode 100644 index 000000000..e3ba4bd86 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.MSTest/Microsoft.CodeAnalysis.Testing.Verifiers.MSTest.csproj @@ -0,0 +1,24 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.Testing.Verifiers.MSTest + Microsoft.CodeAnalysis.Testing.Verifiers + + + + true + Roslyn Test Verifiers for MSTest. + Roslyn Test Verifiers for MSTest. + Roslyn Test Verifiers for MSTest + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.MSTest/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.MSTest/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.MSTest/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.MSTest/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..7553d2028 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.MSTest/PublicAPI.Unshipped.txt @@ -0,0 +1,14 @@ +Microsoft.CodeAnalysis.Testing.Verifiers.MSTestVerifier +Microsoft.CodeAnalysis.Testing.Verifiers.MSTestVerifier.Context.get -> System.Collections.Immutable.ImmutableStack +Microsoft.CodeAnalysis.Testing.Verifiers.MSTestVerifier.MSTestVerifier() -> void +Microsoft.CodeAnalysis.Testing.Verifiers.MSTestVerifier.MSTestVerifier(System.Collections.Immutable.ImmutableStack context) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.MSTestVerifier.CreateMessage(string message) -> string +virtual Microsoft.CodeAnalysis.Testing.Verifiers.MSTestVerifier.Empty(string collectionName, System.Collections.Generic.IEnumerable collection) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.MSTestVerifier.Equal(T expected, T actual, string message = null) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.MSTestVerifier.Fail(string message = null) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.MSTestVerifier.False(bool assert, string message = null) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.MSTestVerifier.LanguageIsSupported(string language) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.MSTestVerifier.NotEmpty(string collectionName, System.Collections.Generic.IEnumerable collection) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.MSTestVerifier.PushContext(string context) -> Microsoft.CodeAnalysis.Testing.IVerifier +virtual Microsoft.CodeAnalysis.Testing.Verifiers.MSTestVerifier.SequenceEqual(System.Collections.Generic.IEnumerable expected, System.Collections.Generic.IEnumerable actual, System.Collections.Generic.IEqualityComparer equalityComparer = null, string message = null) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.MSTestVerifier.True(bool assert, string message = null) -> void diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.NUnit/Microsoft.CodeAnalysis.Testing.Verifiers.NUnit.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.NUnit/Microsoft.CodeAnalysis.Testing.Verifiers.NUnit.csproj new file mode 100644 index 000000000..c1b270f1b --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.NUnit/Microsoft.CodeAnalysis.Testing.Verifiers.NUnit.csproj @@ -0,0 +1,24 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.Testing.Verifiers.NUnit + Microsoft.CodeAnalysis.Testing.Verifiers + + + + true + Roslyn Test Verifiers for NUnit. + Roslyn Test Verifiers for NUnit. + Roslyn Test Verifiers for NUnit + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.NUnit/NUnitVerifier.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.NUnit/NUnitVerifier.cs new file mode 100644 index 000000000..ac8cd048a --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.NUnit/NUnitVerifier.cs @@ -0,0 +1,156 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using NUnit.Framework; + +namespace Microsoft.CodeAnalysis.Testing.Verifiers +{ + public class NUnitVerifier : IVerifier + { + public NUnitVerifier() + : this(ImmutableStack.Empty) + { + } + + protected NUnitVerifier(ImmutableStack context) + { + Context = context ?? throw new ArgumentNullException(nameof(context)); + } + + protected ImmutableStack Context { get; } + + public virtual void Empty(string collectionName, IEnumerable collection) + { + Assert.IsEmpty(collection, CreateMessage($"Expected '{collectionName}' to be empty, contains '{collection?.Count()}' elements")); + } + + public virtual void Equal(T expected, T actual, string? message = null) + { + if (message is null && Context.IsEmpty) + { + Assert.AreEqual(expected, actual); + } + else + { + Assert.AreEqual(expected, actual, CreateMessage(message)); + } + } + + public virtual void True([DoesNotReturnIf(false)] bool assert, string? message = null) + { + if (message is null && Context.IsEmpty) + { + Assert.IsTrue(assert); + } + else + { + Assert.IsTrue(assert, CreateMessage(message)); + } + } + + public virtual void False([DoesNotReturnIf(true)] bool assert, string? message = null) + { + if (message is null && Context.IsEmpty) + { + Assert.IsFalse(assert); + } + else + { + Assert.IsFalse(assert, CreateMessage(message)); + } + } + + [DoesNotReturn] + public virtual void Fail(string? message = null) + { + if (message is null && Context.IsEmpty) + { + Assert.Fail(); + } + else + { + Assert.Fail(CreateMessage(message)); + } + + throw ExceptionUtilities.Unreachable; + } + + public virtual void LanguageIsSupported(string language) + { + Assert.IsFalse(language != LanguageNames.CSharp && language != LanguageNames.VisualBasic, CreateMessage($"Unsupported Language: '{language}'")); + } + + public virtual void NotEmpty(string collectionName, IEnumerable collection) + { + Assert.IsNotEmpty(collection, CreateMessage($"expected '{collectionName}' to be non-empty, contains")); + } + + public virtual void SequenceEqual(IEnumerable expected, IEnumerable actual, IEqualityComparer? equalityComparer = null, string? message = null) + { + var comparer = new SequenceEqualEnumerableEqualityComparer(equalityComparer); + var areEqual = comparer.Equals(expected, actual); + if (!areEqual) + { + Assert.Fail(CreateMessage(message)); + } + } + + public virtual IVerifier PushContext(string context) + { + Assert.AreEqual(typeof(NUnitVerifier), GetType()); + return new NUnitVerifier(Context.Push(context)); + } + + protected virtual string CreateMessage(string? message) + { + foreach (var frame in Context) + { + message = "Context: " + frame + Environment.NewLine + message; + } + + return message ?? string.Empty; + } + + private sealed class SequenceEqualEnumerableEqualityComparer : IEqualityComparer?> + { + private readonly IEqualityComparer _itemEqualityComparer; + + public SequenceEqualEnumerableEqualityComparer(IEqualityComparer? itemEqualityComparer) + { + _itemEqualityComparer = itemEqualityComparer ?? EqualityComparer.Default; + } + + public bool Equals(IEnumerable? x, IEnumerable? y) + { + if (ReferenceEquals(x, y)) { return true; } + if (x is null || y is null) { return false; } + + return x.SequenceEqual(y, _itemEqualityComparer); + } + + public int GetHashCode(IEnumerable? obj) + { + if (obj is null) + { + return 0; + } + + // From System.Tuple + // + // The suppression is required due to an invalid contract in IEqualityComparer + // https://github.com/dotnet/runtime/issues/30998 + return obj + .Select(item => _itemEqualityComparer.GetHashCode(item!)) + .Aggregate( + 0, + (aggHash, nextHash) => ((aggHash << 5) + aggHash) ^ nextHash); + } + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.NUnit/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.NUnit/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.NUnit/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.NUnit/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..8941bcb83 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.NUnit/PublicAPI.Unshipped.txt @@ -0,0 +1,14 @@ +Microsoft.CodeAnalysis.Testing.Verifiers.NUnitVerifier +Microsoft.CodeAnalysis.Testing.Verifiers.NUnitVerifier.Context.get -> System.Collections.Immutable.ImmutableStack +Microsoft.CodeAnalysis.Testing.Verifiers.NUnitVerifier.NUnitVerifier() -> void +Microsoft.CodeAnalysis.Testing.Verifiers.NUnitVerifier.NUnitVerifier(System.Collections.Immutable.ImmutableStack context) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.NUnitVerifier.CreateMessage(string message) -> string +virtual Microsoft.CodeAnalysis.Testing.Verifiers.NUnitVerifier.Empty(string collectionName, System.Collections.Generic.IEnumerable collection) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.NUnitVerifier.Equal(T expected, T actual, string message = null) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.NUnitVerifier.Fail(string message = null) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.NUnitVerifier.False(bool assert, string message = null) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.NUnitVerifier.LanguageIsSupported(string language) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.NUnitVerifier.NotEmpty(string collectionName, System.Collections.Generic.IEnumerable collection) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.NUnitVerifier.PushContext(string context) -> Microsoft.CodeAnalysis.Testing.IVerifier +virtual Microsoft.CodeAnalysis.Testing.Verifiers.NUnitVerifier.SequenceEqual(System.Collections.Generic.IEnumerable expected, System.Collections.Generic.IEnumerable actual, System.Collections.Generic.IEqualityComparer equalityComparer = null, string message = null) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.NUnitVerifier.True(bool assert, string message = null) -> void diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/EmptyWithMessageException.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/EmptyWithMessageException.cs new file mode 100644 index 000000000..d91b9519d --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/EmptyWithMessageException.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections; +using Xunit.Sdk; + +namespace Microsoft.CodeAnalysis.Testing.Verifiers +{ + public class EmptyWithMessageException : EmptyException + { + public EmptyWithMessageException(IEnumerable collection, string userMessage) + : base(collection) + { + UserMessage = userMessage; + } + + public override string Message + { + get + { + if (string.IsNullOrEmpty(UserMessage)) + { + return base.Message; + } + + return UserMessage + Environment.NewLine + base.Message; + } + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/EqualWithMessageException.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/EqualWithMessageException.cs new file mode 100644 index 000000000..3a14e78ed --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/EqualWithMessageException.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Xunit.Sdk; + +namespace Microsoft.CodeAnalysis.Testing.Verifiers +{ + public class EqualWithMessageException : EqualException + { + public EqualWithMessageException(object? expected, object? actual, string userMessage) + : base(expected, actual) + { + UserMessage = userMessage; + } + + public EqualWithMessageException(string? expected, string? actual, int expectedIndex, int actualIndex, string userMessage) + : base(expected, actual, expectedIndex, actualIndex) + { + UserMessage = userMessage; + } + + public override string Message + { + get + { + if (string.IsNullOrEmpty(UserMessage)) + { + return base.Message; + } + + return UserMessage + Environment.NewLine + base.Message; + } + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit.csproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit.csproj new file mode 100644 index 000000000..2947cb5d1 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit.csproj @@ -0,0 +1,24 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.Testing.Verifiers.XUnit + Microsoft.CodeAnalysis.Testing.Verifiers + + + + true + Roslyn Test Verifiers for xUnit. + Roslyn Test Verifiers for xUnit. + Roslyn Test Verifiers for xUnit + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/NotEmptyWithMessageException.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/NotEmptyWithMessageException.cs new file mode 100644 index 000000000..9204a8b22 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/NotEmptyWithMessageException.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Xunit.Sdk; + +namespace Microsoft.CodeAnalysis.Testing.Verifiers +{ + public class NotEmptyWithMessageException : NotEmptyException + { + public NotEmptyWithMessageException(string userMessage) + { + UserMessage = userMessage; + } + + public override string Message + { + get + { + if (string.IsNullOrEmpty(UserMessage)) + { + return base.Message; + } + + return UserMessage + Environment.NewLine + base.Message; + } + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..2745b0193 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/PublicAPI.Unshipped.txt @@ -0,0 +1,24 @@ +Microsoft.CodeAnalysis.Testing.Verifiers.EmptyWithMessageException +Microsoft.CodeAnalysis.Testing.Verifiers.EmptyWithMessageException.EmptyWithMessageException(System.Collections.IEnumerable collection, string userMessage) -> void +Microsoft.CodeAnalysis.Testing.Verifiers.EqualWithMessageException +Microsoft.CodeAnalysis.Testing.Verifiers.EqualWithMessageException.EqualWithMessageException(object expected, object actual, string userMessage) -> void +Microsoft.CodeAnalysis.Testing.Verifiers.EqualWithMessageException.EqualWithMessageException(string expected, string actual, int expectedIndex, int actualIndex, string userMessage) -> void +Microsoft.CodeAnalysis.Testing.Verifiers.NotEmptyWithMessageException +Microsoft.CodeAnalysis.Testing.Verifiers.NotEmptyWithMessageException.NotEmptyWithMessageException(string userMessage) -> void +Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier +Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier.Context.get -> System.Collections.Immutable.ImmutableStack +Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier.XUnitVerifier() -> void +Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier.XUnitVerifier(System.Collections.Immutable.ImmutableStack context) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier.CreateMessage(string message) -> string +virtual Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier.Empty(string collectionName, System.Collections.Generic.IEnumerable collection) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier.Equal(T expected, T actual, string message = null) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier.Fail(string message = null) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier.False(bool assert, string message = null) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier.LanguageIsSupported(string language) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier.NotEmpty(string collectionName, System.Collections.Generic.IEnumerable collection) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier.PushContext(string context) -> Microsoft.CodeAnalysis.Testing.IVerifier +virtual Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier.SequenceEqual(System.Collections.Generic.IEnumerable expected, System.Collections.Generic.IEnumerable actual, System.Collections.Generic.IEqualityComparer equalityComparer = null, string message = null) -> void +virtual Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier.True(bool assert, string message = null) -> void +override Microsoft.CodeAnalysis.Testing.Verifiers.EmptyWithMessageException.Message.get -> string +override Microsoft.CodeAnalysis.Testing.Verifiers.EqualWithMessageException.Message.get -> string +override Microsoft.CodeAnalysis.Testing.Verifiers.NotEmptyWithMessageException.Message.get -> string diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/XUnitVerifier.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/XUnitVerifier.cs new file mode 100644 index 000000000..d02ba8304 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Testing.Verifiers.XUnit/XUnitVerifier.cs @@ -0,0 +1,185 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Xunit; + +namespace Microsoft.CodeAnalysis.Testing.Verifiers +{ + public class XUnitVerifier : IVerifier + { + public XUnitVerifier() + : this(ImmutableStack.Empty) + { + } + + protected XUnitVerifier(ImmutableStack context) + { + Context = context ?? throw new ArgumentNullException(nameof(context)); + } + + protected ImmutableStack Context { get; } + + public virtual void Empty(string collectionName, IEnumerable collection) + { + using (var enumerator = collection.GetEnumerator()) + { + if (enumerator.MoveNext()) + { + throw new EmptyWithMessageException(collection, CreateMessage($"'{collectionName}' is not empty")); + } + } + } + + public virtual void Equal(T expected, T actual, string? message = null) + { + if (message is null && Context.IsEmpty) + { + Assert.Equal(expected, actual); + } + else + { + if (!EqualityComparer.Default.Equals(expected, actual)) + { + throw new EqualWithMessageException(expected, actual, CreateMessage(message)); + } + } + } + + public virtual void True([DoesNotReturnIf(false)] bool assert, string? message = null) + { + if (message is null && Context.IsEmpty) + { + Assert.True(assert); + } + else + { + Assert.True(assert, CreateMessage(message)); + } + } + + public virtual void False([DoesNotReturnIf(true)] bool assert, string? message = null) + { + if (message is null && Context.IsEmpty) + { + Assert.False(assert); + } + else + { + Assert.False(assert, CreateMessage(message)); + } + } + + [DoesNotReturn] + public virtual void Fail(string? message = null) + { + if (message is null && Context.IsEmpty) + { + Assert.True(false); + } + else + { + Assert.True(false, CreateMessage(message)); + } + + throw ExceptionUtilities.Unreachable; + } + + public virtual void LanguageIsSupported(string language) + { + Assert.False(language != LanguageNames.CSharp && language != LanguageNames.VisualBasic, CreateMessage($"Unsupported Language: '{language}'")); + } + + public virtual void NotEmpty(string collectionName, IEnumerable collection) + { + using (var enumerator = collection.GetEnumerator()) + { + if (!enumerator.MoveNext()) + { + throw new NotEmptyWithMessageException(CreateMessage($"'{collectionName}' is empty")); + } + } + } + + public virtual void SequenceEqual(IEnumerable expected, IEnumerable actual, IEqualityComparer? equalityComparer = null, string? message = null) + { + if (message is null && Context.IsEmpty) + { + if (equalityComparer is null) + { + Assert.Equal(expected, actual); + } + else + { + Assert.Equal(expected, actual, equalityComparer); + } + } + else + { + var comparer = new SequenceEqualEnumerableEqualityComparer(equalityComparer); + var areEqual = comparer.Equals(expected, actual); + if (!areEqual) + { + throw new EqualWithMessageException(expected, actual, CreateMessage(message)); + } + } + } + + public virtual IVerifier PushContext(string context) + { + Assert.IsType(this); + return new XUnitVerifier(Context.Push(context)); + } + + protected virtual string CreateMessage(string? message) + { + foreach (var frame in Context) + { + message = "Context: " + frame + Environment.NewLine + message; + } + + return message ?? string.Empty; + } + + private sealed class SequenceEqualEnumerableEqualityComparer : IEqualityComparer?> + { + private readonly IEqualityComparer _itemEqualityComparer; + + public SequenceEqualEnumerableEqualityComparer(IEqualityComparer? itemEqualityComparer) + { + _itemEqualityComparer = itemEqualityComparer ?? EqualityComparer.Default; + } + + public bool Equals(IEnumerable? x, IEnumerable? y) + { + if (ReferenceEquals(x, y)) { return true; } + if (x is null || y is null) { return false; } + + return x.SequenceEqual(y, _itemEqualityComparer); + } + + public int GetHashCode(IEnumerable? obj) + { + if (obj is null) + { + return 0; + } + + // From System.Tuple + // + // The suppression is required due to an invalid contract in IEqualityComparer + // https://github.com/dotnet/runtime/issues/30998 + return obj + .Select(item => _itemEqualityComparer.GetHashCode(item!)) + .Aggregate( + 0, + (aggHash, nextHash) => ((aggHash << 5) + aggHash) ^ nextHash); + } + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest/AnalyzerVerifier.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest/AnalyzerVerifier.vb new file mode 100644 index 000000000..821a5c324 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest/AnalyzerVerifier.vb @@ -0,0 +1,9 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.Diagnostics + +Module AnalyzerVerifier + Function Create(Of TAnalyzer As {DiagnosticAnalyzer, New})() As AnalyzerVerifier(Of TAnalyzer) + Return New AnalyzerVerifier(Of TAnalyzer) + End Function +End Module diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest/AnalyzerVerifier`1.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest/AnalyzerVerifier`1.vb new file mode 100644 index 000000000..30f19ca2d --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest/AnalyzerVerifier`1.vb @@ -0,0 +1,8 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing.Verifiers + +Public Class AnalyzerVerifier(Of TAnalyzer As {DiagnosticAnalyzer, New}) + Inherits VisualBasicAnalyzerVerifier(Of TAnalyzer, MSTestVerifier) +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest.vbproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest.vbproj new file mode 100644 index 000000000..66f5d71b9 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest.vbproj @@ -0,0 +1,27 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest + Microsoft.CodeAnalysis.VisualBasic.Testing.MSTest + + + + true + Roslyn Analyzer Test Framwork For MSTest. + Roslyn Analyzer Test Framwork For MSTest. + Roslyn Analyzer Test Framwork MSTest + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..79f2af77a --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.CodeAnalysis.VisualBasic.Testing.MSTest.AnalyzerVerifier(Of TAnalyzer) +Microsoft.CodeAnalysis.VisualBasic.Testing.MSTest.AnalyzerVerifier(Of TAnalyzer).New() -> Void diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit/AnalyzerVerifier.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit/AnalyzerVerifier.vb new file mode 100644 index 000000000..821a5c324 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit/AnalyzerVerifier.vb @@ -0,0 +1,9 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.Diagnostics + +Module AnalyzerVerifier + Function Create(Of TAnalyzer As {DiagnosticAnalyzer, New})() As AnalyzerVerifier(Of TAnalyzer) + Return New AnalyzerVerifier(Of TAnalyzer) + End Function +End Module diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit/AnalyzerVerifier`1.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit/AnalyzerVerifier`1.vb new file mode 100644 index 000000000..b2bc60934 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit/AnalyzerVerifier`1.vb @@ -0,0 +1,8 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing.Verifiers + +Public Class AnalyzerVerifier(Of TAnalyzer As {DiagnosticAnalyzer, New}) + Inherits VisualBasicAnalyzerVerifier(Of TAnalyzer, NUnitVerifier) +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit.vbproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit.vbproj new file mode 100644 index 000000000..893f96c7e --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit.vbproj @@ -0,0 +1,26 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit + Microsoft.CodeAnalysis.VisualBasic.Testing.NUnit + + + + true + Roslyn Analyzer Test Framwork For NUnit. + Roslyn Analyzer Test Framwork For NUnit. + Roslyn Analyzer Test Framwork NUnit + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..d10dc5c24 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.CodeAnalysis.VisualBasic.Testing.NUnit.AnalyzerVerifier(Of TAnalyzer) +Microsoft.CodeAnalysis.VisualBasic.Testing.NUnit.AnalyzerVerifier(Of TAnalyzer).New() -> Void diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit/AnalyzerVerifier.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit/AnalyzerVerifier.vb new file mode 100644 index 000000000..821a5c324 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit/AnalyzerVerifier.vb @@ -0,0 +1,9 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.Diagnostics + +Module AnalyzerVerifier + Function Create(Of TAnalyzer As {DiagnosticAnalyzer, New})() As AnalyzerVerifier(Of TAnalyzer) + Return New AnalyzerVerifier(Of TAnalyzer) + End Function +End Module diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit/AnalyzerVerifier`1.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit/AnalyzerVerifier`1.vb new file mode 100644 index 000000000..b0d6eefdd --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit/AnalyzerVerifier`1.vb @@ -0,0 +1,8 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing.Verifiers + +Public Class AnalyzerVerifier(Of TAnalyzer As {DiagnosticAnalyzer, New}) + Inherits VisualBasicAnalyzerVerifier(Of TAnalyzer, XUnitVerifier) +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit.vbproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit.vbproj new file mode 100644 index 000000000..2f6b493da --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit.vbproj @@ -0,0 +1,27 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit + Microsoft.CodeAnalysis.VisualBasic.Testing.XUnit + + + + true + Roslyn Analyzer Test Framwork For xUnit. + Roslyn Analyzer Test Framwork For xUnit. + Roslyn Analyzer Test Framwork xUnit + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..05e18d7cb --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.CodeAnalysis.VisualBasic.Testing.XUnit.AnalyzerVerifier(Of TAnalyzer) +Microsoft.CodeAnalysis.VisualBasic.Testing.XUnit.AnalyzerVerifier(Of TAnalyzer).New() -> Void diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.vbproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.vbproj new file mode 100644 index 000000000..16e13bb9d --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.vbproj @@ -0,0 +1,24 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing + Microsoft.CodeAnalysis.VisualBasic.Testing + + + + true + Roslyn Analyzer Test Framwork For VisualBasic. + Roslyn Analyzer Test Framwork For VisualBasic. + Roslyn Analyzer Test Framwork VisualBasic + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..dee8d5b39 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing/PublicAPI.Unshipped.txt @@ -0,0 +1,9 @@ +Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicAnalyzerTest(Of TAnalyzer, TVerifier) +Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicAnalyzerTest(Of TAnalyzer, TVerifier).New() -> Void +Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicAnalyzerVerifier(Of TAnalyzer, TVerifier) +Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicAnalyzerVerifier(Of TAnalyzer, TVerifier).New() -> Void +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicAnalyzerTest(Of TAnalyzer, TVerifier).CreateCompilationOptions() -> Microsoft.CodeAnalysis.CompilationOptions +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicAnalyzerTest(Of TAnalyzer, TVerifier).CreateParseOptions() -> Microsoft.CodeAnalysis.ParseOptions +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicAnalyzerTest(Of TAnalyzer, TVerifier).DefaultFileExt() -> String +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicAnalyzerTest(Of TAnalyzer, TVerifier).GetDiagnosticAnalyzers() -> System.Collections.Generic.IEnumerable(Of Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer) +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicAnalyzerTest(Of TAnalyzer, TVerifier).Language() -> String diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing/VisualBasicAnalyzerTest.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing/VisualBasicAnalyzerTest.vb new file mode 100644 index 000000000..a1d8ba1e6 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing/VisualBasicAnalyzerTest.vb @@ -0,0 +1,35 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing + +Public Class VisualBasicAnalyzerTest(Of TAnalyzer As {DiagnosticAnalyzer, New}, TVerifier As {IVerifier, New}) + Inherits AnalyzerTest(Of TVerifier) + + Private Shared ReadOnly DefaultLanguageVersion As LanguageVersion = + If([Enum].TryParse("Default", DefaultLanguageVersion), DefaultLanguageVersion, LanguageVersion.VisualBasic14) + + Public Overrides ReadOnly Property Language As String + Get + Return LanguageNames.VisualBasic + End Get + End Property + + Protected Overrides ReadOnly Property DefaultFileExt As String + Get + Return "vb" + End Get + End Property + + Protected Overrides Function CreateCompilationOptions() As CompilationOptions + Return New VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + End Function + + Protected Overrides Function CreateParseOptions() As ParseOptions + Return New VisualBasicParseOptions(DefaultLanguageVersion, DocumentationMode.Diagnose) + End Function + + Protected Overrides Function GetDiagnosticAnalyzers() As IEnumerable(Of DiagnosticAnalyzer) + Return New TAnalyzer() {New TAnalyzer()} + End Function +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing/VisualBasicAnalyzerVerifier.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing/VisualBasicAnalyzerVerifier.vb new file mode 100644 index 000000000..6e84981cb --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing/VisualBasicAnalyzerVerifier.vb @@ -0,0 +1,8 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing + +Public Class VisualBasicAnalyzerVerifier(Of TAnalyzer As {DiagnosticAnalyzer, New}, TVerifier As {IVerifier, New}) + Inherits AnalyzerVerifier(Of TAnalyzer, VisualBasicAnalyzerTest(Of TAnalyzer, TVerifier), TVerifier) +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest/CodeFixVerifier.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest/CodeFixVerifier.vb new file mode 100644 index 000000000..4c56af983 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest/CodeFixVerifier.vb @@ -0,0 +1,10 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Diagnostics + +Module CodeFixVerifier + Public Function Create(Of TAnalyzer As {DiagnosticAnalyzer, New}, TCodeFix As {CodeFixProvider, New})() As CodeFixVerifier(Of TAnalyzer, TCodeFix) + Return New CodeFixVerifier(Of TAnalyzer, TCodeFix) + End Function +End Module diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest/CodeFixVerifier`2.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest/CodeFixVerifier`2.vb new file mode 100644 index 000000000..a3de62723 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest/CodeFixVerifier`2.vb @@ -0,0 +1,9 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing.Verifiers + +Public Class CodeFixVerifier(Of TAnalyzer As {DiagnosticAnalyzer, New}, TCodeFix As {CodeFixProvider, New}) + Inherits VisualBasicCodeFixVerifier(Of TAnalyzer, TCodeFix, MSTestVerifier) +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest.vbproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest.vbproj new file mode 100644 index 000000000..a21e677bd --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest.vbproj @@ -0,0 +1,28 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest + Microsoft.CodeAnalysis.VisualBasic.Testing.MSTest + + + + true + Roslyn Code Fix Test Framwork For MSTest. + Roslyn Code Fix Test Framwork For MSTest. + Roslyn Code Fix Test Framwork MSTest + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..c46f7e278 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.CodeAnalysis.VisualBasic.Testing.MSTest.CodeFixVerifier(Of TAnalyzer, TCodeFix) +Microsoft.CodeAnalysis.VisualBasic.Testing.MSTest.CodeFixVerifier(Of TAnalyzer, TCodeFix).New() -> Void diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit/CodeFixVerifier.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit/CodeFixVerifier.vb new file mode 100644 index 000000000..4c56af983 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit/CodeFixVerifier.vb @@ -0,0 +1,10 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Diagnostics + +Module CodeFixVerifier + Public Function Create(Of TAnalyzer As {DiagnosticAnalyzer, New}, TCodeFix As {CodeFixProvider, New})() As CodeFixVerifier(Of TAnalyzer, TCodeFix) + Return New CodeFixVerifier(Of TAnalyzer, TCodeFix) + End Function +End Module diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit/CodeFixVerifier`2.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit/CodeFixVerifier`2.vb new file mode 100644 index 000000000..ba85c684f --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit/CodeFixVerifier`2.vb @@ -0,0 +1,9 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing.Verifiers + +Public Class CodeFixVerifier(Of TAnalyzer As {DiagnosticAnalyzer, New}, TCodeFix As {CodeFixProvider, New}) + Inherits VisualBasicCodeFixVerifier(Of TAnalyzer, TCodeFix, NUnitVerifier) +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit.vbproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit.vbproj new file mode 100644 index 000000000..d4baf501f --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit.vbproj @@ -0,0 +1,28 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit + Microsoft.CodeAnalysis.VisualBasic.Testing.NUnit + + + + true + Roslyn Code Fix Test Framwork For NUnit. + Roslyn Code Fix Test Framwork For NUnit. + Roslyn Code Fix Test Framwork NUnit + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..faa728ac0 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.CodeAnalysis.VisualBasic.Testing.NUnit.CodeFixVerifier(Of TAnalyzer, TCodeFix) +Microsoft.CodeAnalysis.VisualBasic.Testing.NUnit.CodeFixVerifier(Of TAnalyzer, TCodeFix).New() -> Void diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit/CodeFixVerifier.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit/CodeFixVerifier.vb new file mode 100644 index 000000000..4c56af983 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit/CodeFixVerifier.vb @@ -0,0 +1,10 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Diagnostics + +Module CodeFixVerifier + Public Function Create(Of TAnalyzer As {DiagnosticAnalyzer, New}, TCodeFix As {CodeFixProvider, New})() As CodeFixVerifier(Of TAnalyzer, TCodeFix) + Return New CodeFixVerifier(Of TAnalyzer, TCodeFix) + End Function +End Module diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit/CodeFixVerifier`2.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit/CodeFixVerifier`2.vb new file mode 100644 index 000000000..ea978fd67 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit/CodeFixVerifier`2.vb @@ -0,0 +1,9 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing.Verifiers + +Public Class CodeFixVerifier(Of TAnalyzer As {DiagnosticAnalyzer, New}, TCodeFix As {CodeFixProvider, New}) + Inherits VisualBasicCodeFixVerifier(Of TAnalyzer, TCodeFix, XUnitVerifier) +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit.vbproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit.vbproj new file mode 100644 index 000000000..9dfe0c41d --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit.vbproj @@ -0,0 +1,28 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit + Microsoft.CodeAnalysis.VisualBasic.Testing.XUnit + + + + true + Roslyn Code Fix Test Framwork For xUnit. + Roslyn Code Fix Test Framwork For xUnit. + Roslyn Code Fix Test Framwork xUnit + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..ebe1dfb49 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.CodeAnalysis.VisualBasic.Testing.XUnit.CodeFixVerifier(Of TAnalyzer, TCodeFix) +Microsoft.CodeAnalysis.VisualBasic.Testing.XUnit.CodeFixVerifier(Of TAnalyzer, TCodeFix).New() -> Void diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.vbproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.vbproj new file mode 100644 index 000000000..6637383e0 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.vbproj @@ -0,0 +1,25 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing + Microsoft.CodeAnalysis.VisualBasic.Testing + + + + true + Roslyn Code Fix Test Framwork For Visual Basic. + Roslyn Code Fix Test Framwork For Visual Basic. + Roslyn Code Fix Test Framwork Visual Basic + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..fbb45744b --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing/PublicAPI.Unshipped.txt @@ -0,0 +1,11 @@ +Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeFixTest(Of TAnalyzer, TCodeFix, TVerifier) +Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeFixTest(Of TAnalyzer, TCodeFix, TVerifier).New() -> Void +Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeFixVerifier(Of TAnalyzer, TCodeFix, TVerifier) +Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeFixVerifier(Of TAnalyzer, TCodeFix, TVerifier).New() -> Void +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeFixTest(Of TAnalyzer, TCodeFix, TVerifier).CreateCompilationOptions() -> Microsoft.CodeAnalysis.CompilationOptions +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeFixTest(Of TAnalyzer, TCodeFix, TVerifier).CreateParseOptions() -> Microsoft.CodeAnalysis.ParseOptions +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeFixTest(Of TAnalyzer, TCodeFix, TVerifier).DefaultFileExt() -> String +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeFixTest(Of TAnalyzer, TCodeFix, TVerifier).GetCodeFixProviders() -> System.Collections.Generic.IEnumerable(Of Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider) +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeFixTest(Of TAnalyzer, TCodeFix, TVerifier).GetDiagnosticAnalyzers() -> System.Collections.Generic.IEnumerable(Of Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer) +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeFixTest(Of TAnalyzer, TCodeFix, TVerifier).Language() -> String +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeFixTest(Of TAnalyzer, TCodeFix, TVerifier).SyntaxKindType() -> System.Type diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing/VisualBasicCodeFixTest.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing/VisualBasicCodeFixTest.vb new file mode 100644 index 000000000..d71cc33b8 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing/VisualBasicCodeFixTest.vb @@ -0,0 +1,46 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing + +Public Class VisualBasicCodeFixTest(Of TAnalyzer As {DiagnosticAnalyzer, New}, TCodeFix As {CodeFixProvider, New}, TVerifier As {IVerifier, New}) + Inherits CodeFixTest(Of TVerifier) + + Private Shared ReadOnly DefaultLanguageVersion As LanguageVersion = + If([Enum].TryParse("Default", DefaultLanguageVersion), DefaultLanguageVersion, LanguageVersion.VisualBasic14) + + Public Overrides ReadOnly Property Language As String + Get + Return LanguageNames.VisualBasic + End Get + End Property + + Public Overrides ReadOnly Property SyntaxKindType As Type + Get + Return GetType(SyntaxKind) + End Get + End Property + + Protected Overrides ReadOnly Property DefaultFileExt As String + Get + Return "vb" + End Get + End Property + + Protected Overrides Function CreateCompilationOptions() As CompilationOptions + Return New VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + End Function + + Protected Overrides Function CreateParseOptions() As ParseOptions + Return New VisualBasicParseOptions(DefaultLanguageVersion, DocumentationMode.Diagnose) + End Function + + Protected Overrides Function GetCodeFixProviders() As IEnumerable(Of CodeFixProvider) + Return New TCodeFix() {New TCodeFix()} + End Function + + Protected Overrides Function GetDiagnosticAnalyzers() As IEnumerable(Of DiagnosticAnalyzer) + Return New TAnalyzer() {New TAnalyzer()} + End Function +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing/VisualBasicCodefixVerifier.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing/VisualBasicCodefixVerifier.vb new file mode 100644 index 000000000..d170433a0 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing/VisualBasicCodefixVerifier.vb @@ -0,0 +1,9 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing + +Public Class VisualBasicCodeFixVerifier(Of TAnalyzer As {DiagnosticAnalyzer, New}, TCodeFix As {CodeFixProvider, New}, TVerifier As {IVerifier, New}) + Inherits CodeFixVerifier(Of TAnalyzer, TCodeFix, VisualBasicCodeFixTest(Of TAnalyzer, TCodeFix, TVerifier), TVerifier) +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest/CodeRefactoringVerifier.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest/CodeRefactoringVerifier.vb new file mode 100644 index 000000000..ad363ea08 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest/CodeRefactoringVerifier.vb @@ -0,0 +1,9 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.CodeRefactorings + +Module CodeRefactoringVerifier + Public Function Create(Of TCodeRefactoring As {CodeRefactoringProvider, New})() As CodeRefactoringVerifier(Of TCodeRefactoring) + Return New CodeRefactoringVerifier(Of TCodeRefactoring) + End Function +End Module diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest/CodeRefactoringVerifier`1.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest/CodeRefactoringVerifier`1.vb new file mode 100644 index 000000000..5197a8ef6 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest/CodeRefactoringVerifier`1.vb @@ -0,0 +1,8 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Testing.Verifiers + +Public Class CodeRefactoringVerifier(Of TCodeRefactoring As {CodeRefactoringProvider, New}) + Inherits VisualBasicCodeRefactoringVerifier(Of TCodeRefactoring, MSTestVerifier) +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest.vbproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest.vbproj new file mode 100644 index 000000000..54805d6f7 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest.vbproj @@ -0,0 +1,27 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.VisualBasic.Testing.MSTest + + + + true + Roslyn Code Refactoring Test Framwork For MSTest. + Roslyn Code Refactoring Test Framwork For MSTest. + Roslyn Code Refactoring Test Framwork MSTest + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..3fe34b979 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.CodeAnalysis.VisualBasic.Testing.MSTest.CodeRefactoringVerifier(Of TCodeRefactoring) +Microsoft.CodeAnalysis.VisualBasic.Testing.MSTest.CodeRefactoringVerifier(Of TCodeRefactoring).New() -> Void diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit/CodeRefactoringVerifier.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit/CodeRefactoringVerifier.vb new file mode 100644 index 000000000..ad363ea08 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit/CodeRefactoringVerifier.vb @@ -0,0 +1,9 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.CodeRefactorings + +Module CodeRefactoringVerifier + Public Function Create(Of TCodeRefactoring As {CodeRefactoringProvider, New})() As CodeRefactoringVerifier(Of TCodeRefactoring) + Return New CodeRefactoringVerifier(Of TCodeRefactoring) + End Function +End Module diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit/CodeRefactoringVerifier`1.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit/CodeRefactoringVerifier`1.vb new file mode 100644 index 000000000..f8825510f --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit/CodeRefactoringVerifier`1.vb @@ -0,0 +1,8 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Testing.Verifiers + +Public Class CodeRefactoringVerifier(Of TCodeRefactoring As {CodeRefactoringProvider, New}) + Inherits VisualBasicCodeRefactoringVerifier(Of TCodeRefactoring, NUnitVerifier) +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit.vbproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit.vbproj new file mode 100644 index 000000000..552639e84 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit.vbproj @@ -0,0 +1,27 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.VisualBasic.Testing.NUnit + + + + true + Roslyn Code Refactoring Test Framwork For NUnit. + Roslyn Code Refactoring Test Framwork For NUnit. + Roslyn Code Refactoring Test Framwork NUnit + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..8beb35f0a --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.CodeAnalysis.VisualBasic.Testing.NUnit.CodeRefactoringVerifier(Of TCodeRefactoring) +Microsoft.CodeAnalysis.VisualBasic.Testing.NUnit.CodeRefactoringVerifier(Of TCodeRefactoring).New() -> Void diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit/CodeRefactoringVerifier.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit/CodeRefactoringVerifier.vb new file mode 100644 index 000000000..ad363ea08 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit/CodeRefactoringVerifier.vb @@ -0,0 +1,9 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.CodeRefactorings + +Module CodeRefactoringVerifier + Public Function Create(Of TCodeRefactoring As {CodeRefactoringProvider, New})() As CodeRefactoringVerifier(Of TCodeRefactoring) + Return New CodeRefactoringVerifier(Of TCodeRefactoring) + End Function +End Module diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit/CodeRefactoringVerifier`1.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit/CodeRefactoringVerifier`1.vb new file mode 100644 index 000000000..5484d099f --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit/CodeRefactoringVerifier`1.vb @@ -0,0 +1,8 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Testing.Verifiers + +Public Class CodeRefactoringVerifier(Of TCodeRefactoring As {CodeRefactoringProvider, New}) + Inherits VisualBasicCodeRefactoringVerifier(Of TCodeRefactoring, XUnitVerifier) +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit.vbproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit.vbproj new file mode 100644 index 000000000..5161bb8ee --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit.vbproj @@ -0,0 +1,27 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.VisualBasic.Testing.XUnit + + + + true + Roslyn Code Refactoring Test Framwork For xUnit. + Roslyn Code Refactoring Test Framwork For xUnit. + Roslyn Code Refactoring Test Framwork xUnit + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..5dee98641 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.CodeAnalysis.VisualBasic.Testing.XUnit.CodeRefactoringVerifier(Of TCodeRefactoring) +Microsoft.CodeAnalysis.VisualBasic.Testing.XUnit.CodeRefactoringVerifier(Of TCodeRefactoring).New() -> Void diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.vbproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.vbproj new file mode 100644 index 000000000..a48be56a6 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.vbproj @@ -0,0 +1,24 @@ + + + + $(TestingLibraryTargetFrameworks) + Microsoft.CodeAnalysis.VisualBasic.Testing + + + + true + Roslyn Code Refactoring Test Framwork For Visual Basic. + Roslyn Code Refactoring Test Framwork For Visual Basic. + Roslyn Code Refactoring Test Framwork Visual Basic + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..0886f91c0 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing/PublicAPI.Unshipped.txt @@ -0,0 +1,10 @@ +Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeRefactoringTest(Of TCodeRefactoring, TVerifier) +Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeRefactoringTest(Of TCodeRefactoring, TVerifier).New() -> Void +Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeRefactoringVerifier(Of TCodeRefactoring, TVerifier) +Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeRefactoringVerifier(Of TCodeRefactoring, TVerifier).New() -> Void +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeRefactoringTest(Of TCodeRefactoring, TVerifier).CreateCompilationOptions() -> Microsoft.CodeAnalysis.CompilationOptions +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeRefactoringTest(Of TCodeRefactoring, TVerifier).CreateParseOptions() -> Microsoft.CodeAnalysis.ParseOptions +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeRefactoringTest(Of TCodeRefactoring, TVerifier).DefaultFileExt() -> String +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeRefactoringTest(Of TCodeRefactoring, TVerifier).GetCodeRefactoringProviders() -> System.Collections.Generic.IEnumerable(Of Microsoft.CodeAnalysis.CodeRefactorings.CodeRefactoringProvider) +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeRefactoringTest(Of TCodeRefactoring, TVerifier).Language() -> String +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicCodeRefactoringTest(Of TCodeRefactoring, TVerifier).SyntaxKindType() -> System.Type diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing/VisualBasicCodeRefactoringTest`2.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing/VisualBasicCodeRefactoringTest`2.vb new file mode 100644 index 000000000..44a2cde85 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing/VisualBasicCodeRefactoringTest`2.vb @@ -0,0 +1,41 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Testing + +Public Class VisualBasicCodeRefactoringTest(Of TCodeRefactoring As {CodeRefactoringProvider, New}, TVerifier As {IVerifier, New}) + Inherits CodeRefactoringTest(Of TVerifier) + + Private Shared ReadOnly DefaultLanguageVersion As LanguageVersion = + If([Enum].TryParse("Default", DefaultLanguageVersion), DefaultLanguageVersion, LanguageVersion.VisualBasic14) + + Public Overrides ReadOnly Property Language As String + Get + Return LanguageNames.VisualBasic + End Get + End Property + + Public Overrides ReadOnly Property SyntaxKindType As Type + Get + Return GetType(SyntaxKind) + End Get + End Property + + Protected Overrides ReadOnly Property DefaultFileExt As String + Get + Return "vb" + End Get + End Property + + Protected Overrides Function CreateCompilationOptions() As CompilationOptions + Return New VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + End Function + + Protected Overrides Function CreateParseOptions() As ParseOptions + Return New VisualBasicParseOptions(DefaultLanguageVersion, DocumentationMode.Diagnose) + End Function + + Protected Overrides Function GetCodeRefactoringProviders() As IEnumerable(Of CodeRefactoringProvider) + Return New TCodeRefactoring() {New TCodeRefactoring()} + End Function +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing/VisualBasicCodeRefactoringVerifier`2.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing/VisualBasicCodeRefactoringVerifier`2.vb new file mode 100644 index 000000000..5f0fe92f5 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing/VisualBasicCodeRefactoringVerifier`2.vb @@ -0,0 +1,8 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Testing + +Public Class VisualBasicCodeRefactoringVerifier(Of TCodeRefactoring As {CodeRefactoringProvider, New}, TVerifier As {IVerifier, New}) + Inherits CodeRefactoringVerifier(Of TCodeRefactoring, VisualBasicCodeRefactoringTest(Of TCodeRefactoring, TVerifier), TVerifier) +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest.vbproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest.vbproj new file mode 100644 index 000000000..0d8a3e811 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest.vbproj @@ -0,0 +1,27 @@ + + + + $(TestingLibrarySourceGeneratorTargetFrameworks) + Microsoft.CodeAnalysis.VisualBasic.Testing.MSTest + + + + true + Roslyn Source Generator Test Framwork For MSTest. + Roslyn Source Generator Test Framwork For MSTest. + Roslyn Source Generator Test Framwork MSTest + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..1ea2dbf9d --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.CodeAnalysis.VisualBasic.Testing.MSTest.SourceGeneratorVerifier(Of TSourceGenerator) +Microsoft.CodeAnalysis.VisualBasic.Testing.MSTest.SourceGeneratorVerifier(Of TSourceGenerator).New() -> Void diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest/SourceGeneratorVerifier.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest/SourceGeneratorVerifier.vb new file mode 100644 index 000000000..59f66182c --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest/SourceGeneratorVerifier.vb @@ -0,0 +1,7 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Module SourceGeneratorVerifier + Public Function Create(Of TSourceGenerator As {ISourceGenerator, New})() As SourceGeneratorVerifier(Of TSourceGenerator) + Return New SourceGeneratorVerifier(Of TSourceGenerator) + End Function +End Module diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest/SourceGeneratorVerifier`1.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest/SourceGeneratorVerifier`1.vb new file mode 100644 index 000000000..200590a2d --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest/SourceGeneratorVerifier`1.vb @@ -0,0 +1,7 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.Testing.Verifiers + +Public Class SourceGeneratorVerifier(Of TSourceGenerator As {ISourceGenerator, New}) + Inherits VisualBasicSourceGeneratorVerifier(Of TSourceGenerator, MSTestVerifier) +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit.vbproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit.vbproj new file mode 100644 index 000000000..e0ca8ae2c --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit.vbproj @@ -0,0 +1,27 @@ + + + + $(TestingLibrarySourceGeneratorTargetFrameworks) + Microsoft.CodeAnalysis.VisualBasic.Testing.NUnit + + + + true + Roslyn Source Generator Test Framwork For NUnit. + Roslyn Source Generator Test Framwork For NUnit. + Roslyn Source Generator Test Framwork NUnit + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..717d0317c --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.CodeAnalysis.VisualBasic.Testing.NUnit.SourceGeneratorVerifier(Of TSourceGenerator) +Microsoft.CodeAnalysis.VisualBasic.Testing.NUnit.SourceGeneratorVerifier(Of TSourceGenerator).New() -> Void diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit/SourceGeneratorVerifier.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit/SourceGeneratorVerifier.vb new file mode 100644 index 000000000..59f66182c --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit/SourceGeneratorVerifier.vb @@ -0,0 +1,7 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Module SourceGeneratorVerifier + Public Function Create(Of TSourceGenerator As {ISourceGenerator, New})() As SourceGeneratorVerifier(Of TSourceGenerator) + Return New SourceGeneratorVerifier(Of TSourceGenerator) + End Function +End Module diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit/SourceGeneratorVerifier`1.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit/SourceGeneratorVerifier`1.vb new file mode 100644 index 000000000..203195842 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit/SourceGeneratorVerifier`1.vb @@ -0,0 +1,7 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.Testing.Verifiers + +Public Class SourceGeneratorVerifier(Of TSourceGenerator As {ISourceGenerator, New}) + Inherits VisualBasicSourceGeneratorVerifier(Of TSourceGenerator, NUnitVerifier) +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit.vbproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit.vbproj new file mode 100644 index 000000000..c64d49b06 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit.vbproj @@ -0,0 +1,27 @@ + + + + $(TestingLibrarySourceGeneratorTargetFrameworks) + Microsoft.CodeAnalysis.VisualBasic.Testing.XUnit + + + + true + Roslyn Source Generator Test Framwork For xUnit. + Roslyn Source Generator Test Framwork For xUnit. + Roslyn Source Generator Test Framwork xUnit + + + + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..7876d2d30 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.CodeAnalysis.VisualBasic.Testing.XUnit.SourceGeneratorVerifier(Of TSourceGenerator) +Microsoft.CodeAnalysis.VisualBasic.Testing.XUnit.SourceGeneratorVerifier(Of TSourceGenerator).New() -> Void diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit/SourceGeneratorVerifier.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit/SourceGeneratorVerifier.vb new file mode 100644 index 000000000..59f66182c --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit/SourceGeneratorVerifier.vb @@ -0,0 +1,7 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Module SourceGeneratorVerifier + Public Function Create(Of TSourceGenerator As {ISourceGenerator, New})() As SourceGeneratorVerifier(Of TSourceGenerator) + Return New SourceGeneratorVerifier(Of TSourceGenerator) + End Function +End Module diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit/SourceGeneratorVerifier`1.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit/SourceGeneratorVerifier`1.vb new file mode 100644 index 000000000..4e408d71f --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit/SourceGeneratorVerifier`1.vb @@ -0,0 +1,7 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.Testing.Verifiers + +Public Class SourceGeneratorVerifier(Of TSourceGenerator As {ISourceGenerator, New}) + Inherits VisualBasicSourceGeneratorVerifier(Of TSourceGenerator, XUnitVerifier) +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.vbproj b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.vbproj new file mode 100644 index 000000000..abe2e3eaf --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.vbproj @@ -0,0 +1,24 @@ + + + + $(TestingLibrarySourceGeneratorTargetFrameworks) + Microsoft.CodeAnalysis.VisualBasic.Testing + + + + true + Roslyn Source Generator Test Framwork For Visual Basic. + Roslyn Source Generator Test Framwork For Visual Basic. + Roslyn Source Generator Test Framwork Visual Basic + + + + + + + + + + + + diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing/PublicAPI.Shipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing/PublicAPI.Shipped.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..1a76b560e --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing/PublicAPI.Unshipped.txt @@ -0,0 +1,10 @@ +Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorTest(Of TSourceGenerator, TVerifier) +Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorTest(Of TSourceGenerator, TVerifier).New() -> Void +Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorVerifier(Of TSourceGenerator, TVerifier) +Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorVerifier(Of TSourceGenerator, TVerifier).New() -> Void +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorTest(Of TSourceGenerator, TVerifier).CreateCompilationOptions() -> Microsoft.CodeAnalysis.CompilationOptions +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorTest(Of TSourceGenerator, TVerifier).CreateGeneratorDriver(project As Microsoft.CodeAnalysis.Project, sourceGenerators As System.Collections.Immutable.ImmutableArray(Of Microsoft.CodeAnalysis.ISourceGenerator)) -> Microsoft.CodeAnalysis.GeneratorDriver +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorTest(Of TSourceGenerator, TVerifier).CreateParseOptions() -> Microsoft.CodeAnalysis.ParseOptions +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorTest(Of TSourceGenerator, TVerifier).DefaultFileExt() -> String +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorTest(Of TSourceGenerator, TVerifier).GetSourceGenerators() -> System.Collections.Generic.IEnumerable(Of Microsoft.CodeAnalysis.ISourceGenerator) +Overrides Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorTest(Of TSourceGenerator, TVerifier).Language() -> String diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing/VisualBasicSourceGeneratorTest`2.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing/VisualBasicSourceGeneratorTest`2.vb new file mode 100644 index 000000000..79aa11529 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing/VisualBasicSourceGeneratorTest`2.vb @@ -0,0 +1,43 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports Microsoft.CodeAnalysis.Testing + +Public Class VisualBasicSourceGeneratorTest(Of TSourceGenerator As {ISourceGenerator, New}, TVerifier As {IVerifier, New}) + Inherits SourceGeneratorTest(Of TVerifier) + + Private Shared ReadOnly DefaultLanguageVersion As LanguageVersion = + If([Enum].TryParse("Default", DefaultLanguageVersion), DefaultLanguageVersion, LanguageVersion.VisualBasic14) + + Public Overrides ReadOnly Property Language As String + Get + Return LanguageNames.VisualBasic + End Get + End Property + + Protected Overrides ReadOnly Property DefaultFileExt As String + Get + Return "vb" + End Get + End Property + + Protected Overrides Function CreateGeneratorDriver(project As Project, sourceGenerators As ImmutableArray(Of ISourceGenerator)) As GeneratorDriver + Return VisualBasicGeneratorDriver.Create( + sourceGenerators, + project.AnalyzerOptions.AdditionalFiles, + CType(project.ParseOptions, VisualBasicParseOptions), + project.AnalyzerOptions.AnalyzerConfigOptionsProvider) + End Function + + Protected Overrides Function CreateCompilationOptions() As CompilationOptions + Return New VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + End Function + + Protected Overrides Function CreateParseOptions() As ParseOptions + Return New VisualBasicParseOptions(DefaultLanguageVersion, DocumentationMode.Diagnose) + End Function + + Protected Overrides Function GetSourceGenerators() As IEnumerable(Of ISourceGenerator) + Return New ISourceGenerator() {New TSourceGenerator()} + End Function +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing/VisualBasicSourceGeneratorVerifier`2.vb b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing/VisualBasicSourceGeneratorVerifier`2.vb new file mode 100644 index 000000000..70dd9345f --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing/VisualBasicSourceGeneratorVerifier`2.vb @@ -0,0 +1,7 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.Testing + +Public Class VisualBasicSourceGeneratorVerifier(Of TSourceGenerator As {ISourceGenerator, New}, TVerifier As {IVerifier, New}) + Inherits SourceGeneratorVerifier(Of TSourceGenerator, VisualBasicSourceGeneratorTest(Of TSourceGenerator, TVerifier), TVerifier) +End Class diff --git a/src/Microsoft.CodeAnalysis.Testing/README.md b/src/Microsoft.CodeAnalysis.Testing/README.md new file mode 100644 index 000000000..ff497cfbb --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Testing/README.md @@ -0,0 +1,276 @@ +# Microsoft.CodeAnalysis.Testing + +## Installation + +Separate packages are provided for each language and test framework. + +### Azure Packages feed for prerelease packages + +To reference prerelease packages, add a **NuGet.Config** file to your solution directory containing the following: + +```xml + + + + + + +``` + +### MSTest + +* C# + * Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.MSTest + * Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest + * Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.MSTest + * Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.MSTest +* Visual Basic + * Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.MSTest + * Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.MSTest + * Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.MSTest + * Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.MSTest + +### NUnit + +* C# + * Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit + * Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit + * Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.NUnit + * Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.NUnit +* Visual Basic + * Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.NUnit + * Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.NUnit + * Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.NUnit + * Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.NUnit + +### xUnit.net + +* C# + * Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit + * Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit + * Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing.XUnit + * Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit +* Visual Basic + * Microsoft.CodeAnalysis.VisualBasic.Analyzer.Testing.XUnit + * Microsoft.CodeAnalysis.VisualBasic.CodeFix.Testing.XUnit + * Microsoft.CodeAnalysis.VisualBasic.CodeRefactoring.Testing.XUnit + * Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit + +## Verifier overview + +Testing analyzers and code fixes starts with the selection of a *verifier* helper type. A default analyzer and code fix +verifier types is defined for each test framework and language combination: + +* `AnalyzerVerifier` +* `CodeFixVerifier` + +The verifier types provide limited functionality intended to serve the majority of analyzer and code fix tests: + +1. Creating a `DiagnosticResult` representing a diagnostic reported by an analyzer +2. Executing a "basic use case" analyzer test +3. Executing a "basic use case" code fix test + +Each of the verifier helper types is supported by a *test* helper type, which provides the primary implementation of +each test scenario: + +* `AnalyzerTest` +* `CodeFixTest` + +### Imports + +This document is written on the assumption that users will alias a verifier or code fix type to the name `Verify` within +the context of a test class. + +```csharp +using Verify = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier; +``` + +Users writing tests involving compiler errors may also want to import the static members of the `DiagnosticResult` type, +which provides the helper method `CompilerError` and `CompilerWarning`. + +```csharp +using static Microsoft.CodeAnalysis.Testing.DiagnosticResult; +``` + +## Best practices + +### Use code fix testing when possible + +`VerifyCodeFixAsync` automatically performs several tests related to code fix scenarios: + +1. Verifies that the analyzer reports an expected set of diagnostics +2. Verifies that the code fix result does not contain fixable diagnostics +3. Verifies that the code fix applied to one diagnostic at a time produces the expected output +4. Verifies that Fix All operations, when supported by the code fix provider, produce the expected output + +With this library, separation of analyzer and code fix tests increases complexity and code duplication, and tends to +decrease the overall confidence in the test suite. For analyzers that provide a code fix, it is preferable to only test +with the code fix verifier for test cases where diagnostics are reported in the input source. For test cases where no +diagnostic is reported in the input source, `VerifyAnalyzerAsync` remains appropriate. + +## Examples + +### Basic use cases + +Basic use cases are supported by static helper methods in the verifier helper types. These helpers set the following +properties: + +* `TestCode`: the single input source file +* `ExpectedDiagnostics`: the diagnostics expected to appear in the input source file +* `FixedCode`: (code fix tests only) the single output source file produced by applying a code fix to the input +* Other properties are set to their default values + +#### Analyzer with no diagnostics + +```csharp +string testCode = @"original source code"; +await Verify.VerifyAnalyzerAsync(testCode); +``` + +#### Analyzer without a code fix + +```csharp +string testCode = @"original source code"; +var expected = Verify.Diagnostic().WithLocation(1, 7); +await Verify.VerifyAnalyzerAsync(testCode, expected); +``` + +#### Analyzer with a code fix + +```csharp +string testCode = @"original source code"; +string fixedCode = @"fixed source code"; +var expected = Verify.Diagnostic().WithLocation(1, 7); +await Verify.VerifyCodeFixAsync(testCode, expected, fixedCode); +``` + +For cases where the code fix does not always offer a fix for a diagnostic, the `fixedCode` may be set to the `testCode` +to verify that no fix is offered for the scenario: + +```csharp +string testCode = @"original source code"; +var expected = Verify.Diagnostic().WithLocation(1, 7); + +// Verifies that the expected diagnostics are reported, but no code fix is offered +await Verify.VerifyCodeFixAsync(testCode, expected, testCode); +``` + +#### Compiler errors + +```csharp +string testCode = @"void Method() { return }"; +var expected = DiagnosticResult.CompilerError("CS0246").WithLocation(1, 23).WithMessage("; expected"); +await Verify.VerifyAnalyzerAsync(testCode, expected); +``` + +### Advanced use cases + +Advanced use cases involve the instantiation of a test helper, setting the appropriate properties, and finally calling +`RunAsync` to run the test. These steps can be combined using the object initializer syntax: + +```csharp +await new CSharpAnalyzerTest +{ + // Configure test by setting property values here... +}.RunAsync(); +``` + +#### Additional files + +```csharp +await new CSharpAnalyzerTest +{ + TestState = + { + Sources = { @"original source code" }, + ExpectedDiagnostics = { Verify.Diagnostic().WithLocation(1, 7) }, + AdditionalFiles = + { + ("File1.ext", "content1"), + ("File2.ext", "content2"), + }, + }, +}.RunAsync(); +``` + +#### Overriding the default verifier behavior + +💡 If the same set of steps needs to be performed for each test in a test suite, consider creating a customized set of +verifier and test types. For example, [Microsoft/vs-threading](https://github.com/Microsoft/vs-threading) uses specific +Additional Files and metadata references in most of its tests, so it uses custom verifier and test types to allow the +use of "basic use cases" for test scenarios that would otherwise be considered advanced. + +```csharp +public static class CSharpAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() +{ + public static DiagnosticResult Diagnostic(string diagnosticId = null) + => CSharpAnalyzerVerifier.Diagnostic(diagnosticId); + + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => new DiagnosticResult(descriptor); + + public static Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new Test { TestCode = source }; + test.ExpectedDiagnostics.AddRange(expected); + return test.RunAsync(); + } + + // Code fix tests support both analyzer and code fix testing. This test class is derived from the code fix test + // to avoid the need to maintain duplicate copies of the customization work. + public class Test : CSharpCodeFixTest + { + } +} + +public static class CSharpCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() +{ + public static DiagnosticResult Diagnostic(string diagnosticId = null) + => CSharpCodeFixVerifier.Diagnostic(diagnosticId); + + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => new DiagnosticResult(descriptor); + + public static Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new CSharpAnalyzerVerifier.Test { TestCode = source }; + test.ExpectedDiagnostics.AddRange(expected); + return test.RunAsync(); + } + + public static Task VerifyCodeFixAsync(string source, string fixedSource) + => VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + + public static Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource) + => VerifyCodeFixAsync(source, new[] { expected }, fixedSource); + + public static Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + return test.RunAsync(); + } + + public class Test : CSharpCodeFixTest + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + public CSharpCodeFixTest() + { + // Custom initialization logic here + } + + // Custom analyzers and/or code fix properties here + } +} + +``` + diff --git a/src/Templates/VS2015/CSRef.vstemplate b/src/Templates/VS2015/CSRef.vstemplate deleted file mode 100644 index cd9008560..000000000 --- a/src/Templates/VS2015/CSRef.vstemplate +++ /dev/null @@ -1,34 +0,0 @@ - - - Code Refactoring (Portable Class Library) - Create a C# refactoring, deployed as a VSIX extension - CSharp - - 1000 - true - CodeRefactoring - true - Enabled - true - CodeInformation.ico - true - - - - - - ProjectTemplates\CSharp\CodeRefactoring\Ref\CSharpCodeRefactoring.vstemplate - - - ProjectTemplates\CSharp\CodeRefactoring\Vsix\Deployment.vstemplate - - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKRootTemplateWizard - - \ No newline at end of file diff --git a/src/Templates/VS2015/CSharpDiagnostic.vstemplate b/src/Templates/VS2015/CSharpDiagnostic.vstemplate deleted file mode 100644 index 4a304fd23..000000000 --- a/src/Templates/VS2015/CSharpDiagnostic.vstemplate +++ /dev/null @@ -1,33 +0,0 @@ - - - Analyzer with Code Fix (Portable Class Library) - Create a C# analyzer that comes with a code fix and generates diagnostics. The analyzer can be deployed as either a NuGet package or a VSIX extension. - CSharp - - 1000 - Analyzer - true - CodeInformation.ico - - - - - - ProjectTemplates\CSharp\Diagnostic\Analyzer\DiagnosticAnalyzer.vstemplate - - - ProjectTemplates\CSharp\Diagnostic\Test\Test.vstemplate - - - ProjectTemplates\CSharp\Diagnostic\Vsix\Deployment.vstemplate - - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKRootTemplateWizard - - \ No newline at end of file diff --git a/src/Templates/VS2015/Directory.Build.props b/src/Templates/VS2015/Directory.Build.props deleted file mode 100644 index 2a281d3d1..000000000 --- a/src/Templates/VS2015/Directory.Build.props +++ /dev/null @@ -1,13 +0,0 @@ - - - - Templates\$(MSBuildProjectName) - - - - false - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ItemTemplates/CSharp/Analyzer/Analyzer.cs b/src/Templates/VS2015/ItemTemplates/CSharp/Analyzer/Analyzer.cs deleted file mode 100644 index fd52005aa..000000000 --- a/src/Templates/VS2015/ItemTemplates/CSharp/Analyzer/Analyzer.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace $rootnamespace$ -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class $safeitemname$ : DiagnosticAnalyzer - { - public const string DiagnosticId = "$safeitemname$"; - internal static readonly LocalizableString Title = "$safeitemname$ Title"; - internal static readonly LocalizableString MessageFormat = "$safeitemname$ '{0}'"; - internal const string Category = "$safeitemname$ Category"; - - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, true); - - public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } } - - public override void Initialize(AnalysisContext context) - { - } - } -} diff --git a/src/Templates/VS2015/ItemTemplates/CSharp/Analyzer/CSharpAnalyzer.vstemplate b/src/Templates/VS2015/ItemTemplates/CSharp/Analyzer/CSharpAnalyzer.vstemplate deleted file mode 100644 index c762c2cf0..000000000 --- a/src/Templates/VS2015/ItemTemplates/CSharp/Analyzer/CSharpAnalyzer.vstemplate +++ /dev/null @@ -1,14 +0,0 @@ - - - Microsoft.CSharp.Class - Analyzer.cs - Analyzer - Create a C# diagnostic analyzer. - CSharp - 10 - - - - Analyzer.cs - - \ No newline at end of file diff --git a/src/Templates/VS2015/ItemTemplates/CSharp/CodeFix/CSharpCodeFix.vstemplate b/src/Templates/VS2015/ItemTemplates/CSharp/CodeFix/CSharpCodeFix.vstemplate deleted file mode 100644 index a9cee2198..000000000 --- a/src/Templates/VS2015/ItemTemplates/CSharp/CodeFix/CSharpCodeFix.vstemplate +++ /dev/null @@ -1,14 +0,0 @@ - - - Microsoft.CSharp.Class - CodeFix.cs - CodeFix - Create a C# code fix. - CSharp - 10 - - - - CodeFix.cs - - \ No newline at end of file diff --git a/src/Templates/VS2015/ItemTemplates/CSharp/CodeFix/CodeFix.cs b/src/Templates/VS2015/ItemTemplates/CSharp/CodeFix/CodeFix.cs deleted file mode 100644 index 489b3743e..000000000 --- a/src/Templates/VS2015/ItemTemplates/CSharp/CodeFix/CodeFix.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Composition; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Rename; -using Microsoft.CodeAnalysis.Text; - -namespace $rootnamespace$ -{ - [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof($safeitemname$)), Shared] - public class $safeitemname$ : CodeFixProvider - { - // TODO: Replace with actual diagnostic id that should trigger this fix. - public const string DiagnosticId = "$safeitemname$"; - - public sealed override ImmutableArray FixableDiagnosticIds - { - get { return ImmutableArray.Create(DiagnosticId); } - } - - public sealed override FixAllProvider GetFixAllProvider() - { - return WellKnownFixAllProviders.BatchFixer; - } - - public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Templates/VS2015/ItemTemplates/CSharp/Refactoring/CSharpRefactoring.vstemplate b/src/Templates/VS2015/ItemTemplates/CSharp/Refactoring/CSharpRefactoring.vstemplate deleted file mode 100644 index e5a6b6392..000000000 --- a/src/Templates/VS2015/ItemTemplates/CSharp/Refactoring/CSharpRefactoring.vstemplate +++ /dev/null @@ -1,14 +0,0 @@ - - - Microsoft.CSharp.Class - Refactoring.cs - Refactoring - Create a C# code refactoring. - CSharp - 10 - - - - Refactoring.cs - - \ No newline at end of file diff --git a/src/Templates/VS2015/ItemTemplates/CSharp/Refactoring/Refactoring.cs b/src/Templates/VS2015/ItemTemplates/CSharp/Refactoring/Refactoring.cs deleted file mode 100644 index 82f751124..000000000 --- a/src/Templates/VS2015/ItemTemplates/CSharp/Refactoring/Refactoring.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Composition; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Rename; -using Microsoft.CodeAnalysis.Text; - -namespace $rootnamespace$ -{ - [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof($safeitemname$)), Shared] - internal class $safeitemname$ : CodeRefactoringProvider - { - public sealed override Task ComputeRefactoringsAsync(CodeRefactoringContext context) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Templates/VS2015/ItemTemplates/VisualBasic/Analyzer/Analyzer.vb b/src/Templates/VS2015/ItemTemplates/VisualBasic/Analyzer/Analyzer.vb deleted file mode 100644 index 34725d49c..000000000 --- a/src/Templates/VS2015/ItemTemplates/VisualBasic/Analyzer/Analyzer.vb +++ /dev/null @@ -1,28 +0,0 @@ -Imports System.Collections.Immutable -Imports System.Threading -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Diagnostics -Imports Microsoft.CodeAnalysis.Text -Imports Microsoft.CodeAnalysis.VisualBasic -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax - - -Public Class $safeitemname$ - Inherits DiagnosticAnalyzer - - Public Const DiagnosticId = "$safeitemname$" - Friend Shared ReadOnly Title As LocalizableString = "$safeitemname$ Title" - Friend Shared ReadOnly MessageFormat As LocalizableString = "$safeitemname$ '{0}'" - Friend Const Category = "$safeitemname$ Category" - - Friend Shared Rule As New DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, True) - - Public Overrides ReadOnly Property SupportedDiagnostics As ImmutableArray(Of DiagnosticDescriptor) - Get - Return ImmutableArray.Create(Rule) - End Get - End Property - - Public Overrides Sub Initialize(context As AnalysisContext) - End Sub -End Class diff --git a/src/Templates/VS2015/ItemTemplates/VisualBasic/Analyzer/VBAnalyzer.vstemplate b/src/Templates/VS2015/ItemTemplates/VisualBasic/Analyzer/VBAnalyzer.vstemplate deleted file mode 100644 index da5ef883d..000000000 --- a/src/Templates/VS2015/ItemTemplates/VisualBasic/Analyzer/VBAnalyzer.vstemplate +++ /dev/null @@ -1,15 +0,0 @@ - - - - Microsoft.VisualBasic.Internal.AssemblyInfo - Analyzer.vb - Analyzer - Create a Visual Basic diagnostic analyzer. - VisualBasic - 10 - - - - Analyzer.vb - - \ No newline at end of file diff --git a/src/Templates/VS2015/ItemTemplates/VisualBasic/CodeFix/CodeFix.vb b/src/Templates/VS2015/ItemTemplates/VisualBasic/CodeFix/CodeFix.vb deleted file mode 100644 index 69d440f0b..000000000 --- a/src/Templates/VS2015/ItemTemplates/VisualBasic/CodeFix/CodeFix.vb +++ /dev/null @@ -1,33 +0,0 @@ -Imports System.Composition -Imports System.Collections.Immutable -Imports System.Threading -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.CodeActions -Imports Microsoft.CodeAnalysis.CodeFixes -Imports Microsoft.CodeAnalysis.CodeRefactorings -Imports Microsoft.CodeAnalysis.Rename -Imports Microsoft.CodeAnalysis.Text -Imports Microsoft.CodeAnalysis.VisualBasic -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax - - -Public Class $safeitemname$ - Inherits CodeFixProvider - - ' TODO: Replace with actual diagnostic id that should trigger this fix. - Public Const DiagnosticId As String = "$safeitemname$" - - Public NotOverridable Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) - Get - Return ImmutableArray.Create(DiagnosticId) - End Get - End Property - - Public NotOverridable Overrides Function GetFixAllProvider() As FixAllProvider - Return WellKnownFixAllProviders.BatchFixer - End Function - - Public NotOverridable Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Throw New NotImplementedException() - End Function -End Class diff --git a/src/Templates/VS2015/ItemTemplates/VisualBasic/CodeFix/VBCodeFix.vstemplate b/src/Templates/VS2015/ItemTemplates/VisualBasic/CodeFix/VBCodeFix.vstemplate deleted file mode 100644 index b0fba26ac..000000000 --- a/src/Templates/VS2015/ItemTemplates/VisualBasic/CodeFix/VBCodeFix.vstemplate +++ /dev/null @@ -1,15 +0,0 @@ - - - - Microsoft.VisualBasic.Internal.AssemblyInfo - CodeFix.vb - CodeFix - Create a Visual Basic code fix. - VisualBasic - 10 - - - - CodeFix.vb - - \ No newline at end of file diff --git a/src/Templates/VS2015/ItemTemplates/VisualBasic/Refactoring/Refactoring.vb b/src/Templates/VS2015/ItemTemplates/VisualBasic/Refactoring/Refactoring.vb deleted file mode 100644 index 416dd6e7a..000000000 --- a/src/Templates/VS2015/ItemTemplates/VisualBasic/Refactoring/Refactoring.vb +++ /dev/null @@ -1,18 +0,0 @@ -Imports System.Composition -Imports System.Threading -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.CodeActions -Imports Microsoft.CodeAnalysis.CodeRefactorings -Imports Microsoft.CodeAnalysis.Rename -Imports Microsoft.CodeAnalysis.Text -Imports Microsoft.CodeAnalysis.VisualBasic -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax - - -Friend Class $safeitemname$ - Inherits CodeRefactoringProvider - - Public NotOverridable Overrides Function ComputeRefactoringsAsync(context As CodeRefactoringContext) As Task - Throw New NotImplementedException() - End Function -End Class diff --git a/src/Templates/VS2015/ItemTemplates/VisualBasic/Refactoring/VBRefactoring.vstemplate b/src/Templates/VS2015/ItemTemplates/VisualBasic/Refactoring/VBRefactoring.vstemplate deleted file mode 100644 index 14af755cb..000000000 --- a/src/Templates/VS2015/ItemTemplates/VisualBasic/Refactoring/VBRefactoring.vstemplate +++ /dev/null @@ -1,15 +0,0 @@ - - - - Microsoft.VisualBasic.Internal.AssemblyInfo - Refactoring.vb - Refactoring - Create a Visual Basic refactoring. - VisualBasic - 10 - - - - Refactoring.vb - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Ref/AssemblyInfo.cs b/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Ref/AssemblyInfo.cs deleted file mode 100644 index 1871ffe91..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Ref/AssemblyInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("$projectname$")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("$registeredorganization$")] -[assembly: AssemblyProduct("$projectname$")] -[assembly: AssemblyCopyright("Copyright © $registeredorganization$ $year$")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Ref/CSharpCodeRefactoring.vstemplate b/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Ref/CSharpCodeRefactoring.vstemplate deleted file mode 100644 index a65a05053..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Ref/CSharpCodeRefactoring.vstemplate +++ /dev/null @@ -1,39 +0,0 @@ - - - Code Refactoring (VSIX) - Create a C# refactoring, deployed as a VSIX extension - CSharp - - 951 - true - CodeRefactoring - true - Enabled - true - - true - - - - AssemblyInfo.cs - CodeRefactoringProvider.cs - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKAnalyzerTemplateWizard - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Ref/CodeRefactoring.csproj b/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Ref/CodeRefactoring.csproj deleted file mode 100644 index a4bb581e6..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Ref/CodeRefactoring.csproj +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - 14.0 - Debug - AnyCPU - AnyCPU - 2.0 - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - $guid1$ - Library - Properties - $safeprojectname$ - $safeprojectname$ - Profile7 - v4.5 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Ref/CodeRefactoringProvider.cs b/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Ref/CodeRefactoringProvider.cs deleted file mode 100644 index d80381d0e..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Ref/CodeRefactoringProvider.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Collections.Generic; -using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Rename; -using Microsoft.CodeAnalysis.Text; - -namespace $safeprojectname$ -{ - [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof($saferootidentifiername$CodeRefactoringProvider)), Shared] - internal class $saferootidentifiername$CodeRefactoringProvider : CodeRefactoringProvider - { - public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) - { - // TODO: Replace the following code with your own analysis, generating a CodeAction for each refactoring to offer - - var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - - // Find the node at the selection. - var node = root.FindNode(context.Span); - - // Only offer a refactoring if the selected node is a type declaration node. - var typeDecl = node as TypeDeclarationSyntax; - if (typeDecl == null) - { - return; - } - - // For any type declaration node, create a code action to reverse the identifier text. - var action = CodeAction.Create("Reverse type name", c => ReverseTypeNameAsync(context.Document, typeDecl, c)); - - // Register this code action. - context.RegisterRefactoring(action); - } - - private async Task ReverseTypeNameAsync(Document document, TypeDeclarationSyntax typeDecl, CancellationToken cancellationToken) - { - // Produce a reversed version of the type declaration's identifier token. - var identifierToken = typeDecl.Identifier; - var newName = new string(identifierToken.Text.ToCharArray().Reverse().ToArray()); - - // Get the symbol representing the type to be renamed. - var semanticModel = await document.GetSemanticModelAsync(cancellationToken); - var typeSymbol = semanticModel.GetDeclaredSymbol(typeDecl, cancellationToken); - - // Produce a new solution that has all references to that type renamed, including the declaration. - var originalSolution = document.Project.Solution; - var optionSet = originalSolution.Workspace.Options; - var newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(false); - - // Return the new solution with the now-uppercase type name. - return newSolution; - } - } -} diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Vsix/Deployment.csproj b/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Vsix/Deployment.csproj deleted file mode 100644 index 494897749..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Vsix/Deployment.csproj +++ /dev/null @@ -1,58 +0,0 @@ - - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - Debug - AnyCPU - AnyCPU - 2.0 - {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - $guid1$ - Library - Properties - $saferootprojectname$.Vsix - $saferootprojectname$.Vsix - v$targetframeworkversion$ - false - false - false - false - false - false - Roslyn - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - Program - $(DevEnvDir)devenv.exe - /rootsuffix Roslyn - - - - Designer - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Vsix/Deployment.vstemplate b/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Vsix/Deployment.vstemplate deleted file mode 100644 index c7e467124..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Vsix/Deployment.vstemplate +++ /dev/null @@ -1,23 +0,0 @@ - - - - Vsix - <No description available> - - CSharp - 1000 - bb967cab-2ca5-4dac-8809-65b2b28a6350 - true - Vsix - true - - - - source.extension.vsixmanifest - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKVsixTemplateWizardSecondProject - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Vsix/source.extension.vsixmanifest b/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Vsix/source.extension.vsixmanifest deleted file mode 100644 index 3f3981b9e..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/CodeRefactoring/Vsix/source.extension.vsixmanifest +++ /dev/null @@ -1,21 +0,0 @@ - - - - - $saferootprojectname$ - This is a sample code refactoring extension for the .NET Compiler Platform ("Roslyn"). - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/ConsoleApplication/AssemblyInfo.cs b/src/Templates/VS2015/ProjectTemplates/CSharp/ConsoleApplication/AssemblyInfo.cs deleted file mode 100644 index 9a6ba37d0..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/ConsoleApplication/AssemblyInfo.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("$projectname$")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("$registeredorganization$")] -[assembly: AssemblyProduct("$projectname$")] -[assembly: AssemblyCopyright("Copyright © $registeredorganization$ $year$")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/ConsoleApplication/CSharpConsoleApplication.vstemplate b/src/Templates/VS2015/ProjectTemplates/CSharp/ConsoleApplication/CSharpConsoleApplication.vstemplate deleted file mode 100644 index 413434ef2..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/ConsoleApplication/CSharpConsoleApplication.vstemplate +++ /dev/null @@ -1,40 +0,0 @@ - - - - Stand-Alone Code Analysis Tool (Portable Class Library) - Create a code analysis command-line application - CSharp - - 950 - true - ConsoleApplication - true - Enabled - true - CodeAnalysisConsole.ico - true - - - - AssemblyInfo.cs - Program.cs - - - - NuGet.VisualStudio.Interop, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - NuGet.VisualStudio.TemplateWizard - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/ConsoleApplication/ConsoleApplication.csproj b/src/Templates/VS2015/ProjectTemplates/CSharp/ConsoleApplication/ConsoleApplication.csproj deleted file mode 100644 index fd119310c..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/ConsoleApplication/ConsoleApplication.csproj +++ /dev/null @@ -1,47 +0,0 @@ - - - - Debug - x86 - x86 - 8.0.30703 - 2.0 - $guid1$ - Exe - Properties - $safeprojectname$ - $safeprojectname$ - v$targetframeworkversion$ - 512 - - - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/ConsoleApplication/Program.cs b/src/Templates/VS2015/ProjectTemplates/CSharp/ConsoleApplication/Program.cs deleted file mode 100644 index 4102856c9..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/ConsoleApplication/Program.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; - -namespace $safeprojectname$ -{ - class Program - { - static void Main(string[] args) - { - } - } -} diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/AssemblyInfo.cs b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/AssemblyInfo.cs deleted file mode 100644 index 1871ffe91..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/AssemblyInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("$projectname$")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("$registeredorganization$")] -[assembly: AssemblyProduct("$projectname$")] -[assembly: AssemblyCopyright("Copyright © $registeredorganization$ $year$")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/CodeFixProvider.cs b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/CodeFixProvider.cs deleted file mode 100644 index 6380fb592..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/CodeFixProvider.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Rename; -using Microsoft.CodeAnalysis.Text; - -namespace $saferootprojectname$ -{ - [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof($saferootidentifiername$CodeFixProvider)), Shared] - public class $saferootidentifiername$CodeFixProvider : CodeFixProvider - { - private const string title = "Make uppercase"; - - public sealed override ImmutableArray FixableDiagnosticIds - { - get { return ImmutableArray.Create($saferootidentifiername$Analyzer.DiagnosticId); } - } - - public sealed override FixAllProvider GetFixAllProvider() - { - // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers - return WellKnownFixAllProviders.BatchFixer; - } - - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - - // TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest - var diagnostic = context.Diagnostics.First(); - var diagnosticSpan = diagnostic.Location.SourceSpan; - - // Find the type declaration identified by the diagnostic. - var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); - - // Register a code action that will invoke the fix. - context.RegisterCodeFix( - CodeAction.Create( - title: title, - createChangedSolution: c => MakeUppercaseAsync(context.Document, declaration, c), - equivalenceKey: title), - diagnostic); - } - - private async Task MakeUppercaseAsync(Document document, TypeDeclarationSyntax typeDecl, CancellationToken cancellationToken) - { - // Compute new uppercase name. - var identifierToken = typeDecl.Identifier; - var newName = identifierToken.Text.ToUpperInvariant(); - - // Get the symbol representing the type to be renamed. - var semanticModel = await document.GetSemanticModelAsync(cancellationToken); - var typeSymbol = semanticModel.GetDeclaredSymbol(typeDecl, cancellationToken); - - // Produce a new solution that has all references to that type renamed, including the declaration. - var originalSolution = document.Project.Solution; - var optionSet = originalSolution.Workspace.Options; - var newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(false); - - // Return the new solution with the now-uppercase type name. - return newSolution; - } - } -} diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/Diagnostic.nuspec b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/Diagnostic.nuspec deleted file mode 100644 index d7e845832..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/Diagnostic.nuspec +++ /dev/null @@ -1,27 +0,0 @@ - - - - $saferootprojectname$ - 1.0.0.0 - $saferootprojectname$ - $username$ - $username$ - http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE - http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE - http://ICON_URL_HERE_OR_DELETE_THIS_LINE - false - $saferootprojectname$ - Summary of changes made in this release of the package. - Copyright - $saferootprojectname$, analyzers - - - - true - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.cs b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.cs deleted file mode 100644 index 9b3329cdf..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace $saferootprojectname$ -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class $saferootidentifiername$Analyzer : DiagnosticAnalyzer - { - public const string DiagnosticId = "$saferootidentifiername$"; - - // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat. - // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Localizing%20Analyzers.md for more on localization - private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources)); - private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources)); - private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources)); - private const string Category = "Naming"; - - private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); - - public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } } - - public override void Initialize(AnalysisContext context) - { - // TODO: Consider registering other actions that act on syntax instead of or in addition to symbols - // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Analyzer%20Actions%20Semantics.md for more information - context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType); - } - - private static void AnalyzeSymbol(SymbolAnalysisContext context) - { - // TODO: Replace the following code with your own analysis, generating Diagnostic objects for any issues you find - var namedTypeSymbol = (INamedTypeSymbol)context.Symbol; - - // Find just those named type symbols with names containing lowercase letters. - if (namedTypeSymbol.Name.ToCharArray().Any(char.IsLower)) - { - // For all such symbols, produce a diagnostic. - var diagnostic = Diagnostic.Create(Rule, namedTypeSymbol.Locations[0], namedTypeSymbol.Name); - - context.ReportDiagnostic(diagnostic); - } - } - } -} diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.csproj b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.csproj deleted file mode 100644 index ffcbfa7c3..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.csproj +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - 11.0 - Debug - AnyCPU - AnyCPU - $guid1$ - Library - Properties - $saferootprojectname$ - $safeprojectname$ - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Profile7 - v4.5 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - True - True - Resources.resx - - - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - - Designer - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.vstemplate b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.vstemplate deleted file mode 100644 index 707d0cdf2..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.vstemplate +++ /dev/null @@ -1,44 +0,0 @@ - - - - DiagnosticAnalyzer - <No description available> - - CSharp - 952 - bb967cab-2ca5-4dac-8809-65b2b28a6350 - true - DiagnosticAnalyzer - true - - - - DiagnosticAnalyzer.cs - CodeFixProvider.cs - AssemblyInfo.cs - ReadMe.txt - Diagnostic.nuspec - Resources.resx - Resources.Designer.cs - install.ps1 - uninstall.ps1 - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKAnalyzerTemplateWizard - - - - - - - - - - - - - - - diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/ReadMe.txt b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/ReadMe.txt deleted file mode 100644 index a913a7772..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/ReadMe.txt +++ /dev/null @@ -1,33 +0,0 @@ - -Building this project will produce an analyzer .dll, as well as the -following two ways you may wish to package that analyzer: - * A NuGet package (.nupkg file) that will add your assembly as a - project-local analyzer that participates in builds. - * A VSIX extension (.vsix file) that will apply your analyzer to all projects - and works just in the IDE. - -To debug your analyzer, make sure the default project is the VSIX project and -start debugging. This will deploy the analyzer as a VSIX into another instance -of Visual Studio, which is useful for debugging, even if you intend to produce -a NuGet package. - - -TRYING OUT YOUR NUGET PACKAGE - -To try out the NuGet package: - 1. Create a local NuGet feed by following the instructions here: - > http://docs.nuget.org/docs/creating-packages/hosting-your-own-nuget-feeds - 2. Copy the .nupkg file into that folder. - 3. Open the target project in Visual Studio 2015. - 4. Right-click on the project node in Solution Explorer and choose Manage - NuGet Packages. - 5. Select the NuGet feed you created on the left. - 6. Choose your analyzer from the list and click Install. - -If you want to automatically deploy the .nupkg file to the local feed folder -when you build this project, follow these steps: - 1. Right-click on this project in Solution Explorer and choose 'Unload Project'. - 2. Right-click on this project and click "Edit". - 3. Scroll down to the "AfterBuild" target. - 4. In the "Exec" task, change the value inside "Command" after the -OutputDirectory - path to point to your local NuGet feed folder. \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/Resources.Designer.cs b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/Resources.Designer.cs deleted file mode 100644 index 8f98a010a..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/Resources.Designer.cs +++ /dev/null @@ -1,106 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.0 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace $saferootprojectname$ -{ - using System; - using System.Reflection; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if (object.ReferenceEquals(resourceMan, null)) - { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("$saferootprojectname$.Resources", typeof(Resources).GetTypeInfo().Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { - return resourceCulture; - } - set - { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Type names should be all uppercase.. - /// - internal static string AnalyzerDescription - { - get - { - return ResourceManager.GetString("AnalyzerDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type name '{0}' contains lowercase letters. - /// - internal static string AnalyzerMessageFormat - { - get - { - return ResourceManager.GetString("AnalyzerMessageFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type name contains lowercase letters. - /// - internal static string AnalyzerTitle - { - get - { - return ResourceManager.GetString("AnalyzerTitle", resourceCulture); - } - } - } -} diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/AssemblyInfo.cs b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/AssemblyInfo.cs deleted file mode 100644 index 9a6ba37d0..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/AssemblyInfo.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("$projectname$")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("$registeredorganization$")] -[assembly: AssemblyProduct("$projectname$")] -[assembly: AssemblyCopyright("Copyright © $registeredorganization$ $year$")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/CodeFixVerifier.Helper.cs b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/CodeFixVerifier.Helper.cs deleted file mode 100644 index 6d3204816..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/CodeFixVerifier.Helper.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Simplification; -using System.Collections.Generic; -using System.Linq; -using System.Threading; - -namespace TestHelper -{ - /// - /// Diagnostic Producer class with extra methods dealing with applying codefixes - /// All methods are static - /// - public abstract partial class CodeFixVerifier : DiagnosticVerifier - { - /// - /// Apply the inputted CodeAction to the inputted document. - /// Meant to be used to apply codefixes. - /// - /// The Document to apply the fix on - /// A CodeAction that will be applied to the Document. - /// A Document with the changes from the CodeAction - private static Document ApplyFix(Document document, CodeAction codeAction) - { - var operations = codeAction.GetOperationsAsync(CancellationToken.None).Result; - var solution = operations.OfType().Single().ChangedSolution; - return solution.GetDocument(document.Id); - } - - /// - /// Compare two collections of Diagnostics,and return a list of any new diagnostics that appear only in the second collection. - /// Note: Considers Diagnostics to be the same if they have the same Ids. In the case of multiple diagnostics with the same Id in a row, - /// this method may not necessarily return the new one. - /// - /// The Diagnostics that existed in the code before the CodeFix was applied - /// The Diagnostics that exist in the code after the CodeFix was applied - /// A list of Diagnostics that only surfaced in the code after the CodeFix was applied - private static IEnumerable GetNewDiagnostics(IEnumerable diagnostics, IEnumerable newDiagnostics) - { - var oldArray = diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); - var newArray = newDiagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); - - int oldIndex = 0; - int newIndex = 0; - - while (newIndex < newArray.Length) - { - if (oldIndex < oldArray.Length && oldArray[oldIndex].Id == newArray[newIndex].Id) - { - ++oldIndex; - ++newIndex; - } - else - { - yield return newArray[newIndex++]; - } - } - } - - /// - /// Get the existing compiler diagnostics on the inputted document. - /// - /// The Document to run the compiler diagnostic analyzers on - /// The compiler diagnostics that were found in the code - private static IEnumerable GetCompilerDiagnostics(Document document) - { - return document.GetSemanticModelAsync().Result.GetDiagnostics(); - } - - /// - /// Given a document, turn it into a string based on the syntax root - /// - /// The Document to be converted to a string - /// A string containing the syntax of the Document after formatting - private static string GetStringFromDocument(Document document) - { - var simplifiedDoc = Simplifier.ReduceAsync(document, Simplifier.Annotation).Result; - var root = simplifiedDoc.GetSyntaxRootAsync().Result; - root = Formatter.Format(root, Formatter.Annotation, simplifiedDoc.Project.Solution.Workspace); - return root.GetText().ToString(); - } - } -} - diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/CodeFixVerifier.cs b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/CodeFixVerifier.cs deleted file mode 100644 index 641daa93c..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/CodeFixVerifier.cs +++ /dev/null @@ -1,128 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Collections.Generic; -using System.Linq; -using System.Threading; - -namespace TestHelper -{ - /// - /// Superclass of all Unit tests made for diagnostics with codefixes. - /// Contains methods used to verify correctness of codefixes - /// - public abstract partial class CodeFixVerifier : DiagnosticVerifier - { - /// - /// Returns the codefix being tested (C#) - to be implemented in non-abstract class - /// - /// The CodeFixProvider to be used for CSharp code - protected virtual CodeFixProvider GetCSharpCodeFixProvider() - { - return null; - } - - /// - /// Returns the codefix being tested (VB) - to be implemented in non-abstract class - /// - /// The CodeFixProvider to be used for VisualBasic code - protected virtual CodeFixProvider GetBasicCodeFixProvider() - { - return null; - } - - /// - /// Called to test a C# codefix when applied on the inputted string as a source - /// - /// A class in the form of a string before the CodeFix was applied to it - /// A class in the form of a string after the CodeFix was applied to it - /// Index determining which codefix to apply if there are multiple - /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied - protected void VerifyCSharpFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false) - { - VerifyFix(LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), GetCSharpCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics); - } - - /// - /// Called to test a VB codefix when applied on the inputted string as a source - /// - /// A class in the form of a string before the CodeFix was applied to it - /// A class in the form of a string after the CodeFix was applied to it - /// Index determining which codefix to apply if there are multiple - /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied - protected void VerifyBasicFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false) - { - VerifyFix(LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), GetBasicCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics); - } - - /// - /// General verifier for codefixes. - /// Creates a Document from the source string, then gets diagnostics on it and applies the relevant codefixes. - /// Then gets the string after the codefix is applied and compares it with the expected result. - /// Note: If any codefix causes new diagnostics to show up, the test fails unless allowNewCompilerDiagnostics is set to true. - /// - /// The language the source code is in - /// The analyzer to be applied to the source code - /// The codefix to be applied to the code wherever the relevant Diagnostic is found - /// A class in the form of a string before the CodeFix was applied to it - /// A class in the form of a string after the CodeFix was applied to it - /// Index determining which codefix to apply if there are multiple - /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied - private void VerifyFix(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string oldSource, string newSource, int? codeFixIndex, bool allowNewCompilerDiagnostics) - { - var document = CreateDocument(oldSource, language); - var analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); - var compilerDiagnostics = GetCompilerDiagnostics(document); - var attempts = analyzerDiagnostics.Length; - - for (int i = 0; i < attempts; ++i) - { - var actions = new List(); - var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a), CancellationToken.None); - codeFixProvider.RegisterCodeFixesAsync(context).Wait(); - - if (!actions.Any()) - { - break; - } - - if (codeFixIndex != null) - { - document = ApplyFix(document, actions.ElementAt((int)codeFixIndex)); - break; - } - - document = ApplyFix(document, actions.ElementAt(0)); - analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); - - var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)); - - //check if applying the code fix introduced any new compiler diagnostics - if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any()) - { - // Format and get the compiler diagnostics again so that the locations make sense in the output - document = document.WithSyntaxRoot(Formatter.Format(document.GetSyntaxRootAsync().Result, Formatter.Annotation, document.Project.Solution.Workspace)); - newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)); - - Assert.IsTrue(false, - string.Format("Fix introduced new compiler diagnostics:\r\n{0}\r\n\r\nNew document:\r\n{1}\r\n", - string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString())), - document.GetSyntaxRootAsync().Result.ToFullString())); - } - - //check if there are analyzer diagnostics left after the code fix - if (!analyzerDiagnostics.Any()) - { - break; - } - } - - //after applying all of the code fixes, compare the resulting string to the inputted one - var actual = GetStringFromDocument(document); - Assert.AreEqual(newSource, actual); - } - } -} diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/DiagnosticResult.cs b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/DiagnosticResult.cs deleted file mode 100644 index dde80c43f..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/DiagnosticResult.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Microsoft.CodeAnalysis; -using System; - -namespace TestHelper -{ - /// - /// Location where the diagnostic appears, as determined by path, line number, and column number. - /// - public struct DiagnosticResultLocation - { - public DiagnosticResultLocation(string path, int line, int column) - { - if (line < -1) - { - throw new ArgumentOutOfRangeException(nameof(line), "line must be >= -1"); - } - - if (column < -1) - { - throw new ArgumentOutOfRangeException(nameof(column), "column must be >= -1"); - } - - this.Path = path; - this.Line = line; - this.Column = column; - } - - public string Path { get; } - public int Line { get; } - public int Column { get; } - } - - /// - /// Struct that stores information about a Diagnostic appearing in a source - /// - public struct DiagnosticResult - { - private DiagnosticResultLocation[] locations; - - public DiagnosticResultLocation[] Locations - { - get - { - if (this.locations == null) - { - this.locations = new DiagnosticResultLocation[] { }; - } - return this.locations; - } - - set - { - this.locations = value; - } - } - - public DiagnosticSeverity Severity { get; set; } - - public string Id { get; set; } - - public string Message { get; set; } - - public string Path - { - get - { - return this.Locations.Length > 0 ? this.Locations[0].Path : ""; - } - } - - public int Line - { - get - { - return this.Locations.Length > 0 ? this.Locations[0].Line : -1; - } - } - - public int Column - { - get - { - return this.Locations.Length > 0 ? this.Locations[0].Column : -1; - } - } - } -} diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/DiagnosticVerifier.Helper.cs b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/DiagnosticVerifier.Helper.cs deleted file mode 100644 index de72e54f8..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/DiagnosticVerifier.Helper.cs +++ /dev/null @@ -1,170 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Text; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; - -namespace TestHelper -{ - /// - /// Class for turning strings into documents and getting the diagnostics on them - /// All methods are static - /// - public abstract partial class DiagnosticVerifier - { - private static readonly MetadataReference CorlibReference = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); - private static readonly MetadataReference SystemCoreReference = MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location); - private static readonly MetadataReference CSharpSymbolsReference = MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location); - private static readonly MetadataReference CodeAnalysisReference = MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location); - - internal static string DefaultFilePathPrefix = "Test"; - internal static string CSharpDefaultFileExt = "cs"; - internal static string VisualBasicDefaultExt = "vb"; - internal static string TestProjectName = "TestProject"; - - #region Get Diagnostics - - /// - /// Given classes in the form of strings, their language, and an IDiagnosticAnalyzer to apply to it, return the diagnostics found in the string after converting it to a document. - /// - /// Classes in the form of strings - /// The language the source classes are in - /// The analyzer to be run on the sources - /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location - private static Diagnostic[] GetSortedDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer) - { - return GetSortedDiagnosticsFromDocuments(analyzer, GetDocuments(sources, language)); - } - - /// - /// Given an analyzer and a document to apply it to, run the analyzer and gather an array of diagnostics found in it. - /// The returned diagnostics are then ordered by location in the source document. - /// - /// The analyzer to run on the documents - /// The Documents that the analyzer will be run on - /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location - protected static Diagnostic[] GetSortedDiagnosticsFromDocuments(DiagnosticAnalyzer analyzer, Document[] documents) - { - var projects = new HashSet(); - foreach (var document in documents) - { - projects.Add(document.Project); - } - - var diagnostics = new List(); - foreach (var project in projects) - { - var compilationWithAnalyzers = project.GetCompilationAsync().Result.WithAnalyzers(ImmutableArray.Create(analyzer)); - var diags = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().Result; - foreach (var diag in diags) - { - if (diag.Location == Location.None || diag.Location.IsInMetadata) - { - diagnostics.Add(diag); - } - else - { - for (int i = 0; i < documents.Length; i++) - { - var document = documents[i]; - var tree = document.GetSyntaxTreeAsync().Result; - if (tree == diag.Location.SourceTree) - { - diagnostics.Add(diag); - } - } - } - } - } - - var results = SortDiagnostics(diagnostics); - diagnostics.Clear(); - return results; - } - - /// - /// Sort diagnostics by location in source document - /// - /// The list of Diagnostics to be sorted - /// An IEnumerable containing the Diagnostics in order of Location - private static Diagnostic[] SortDiagnostics(IEnumerable diagnostics) - { - return diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); - } - - #endregion - - #region Set up compilation and documents - /// - /// Given an array of strings as sources and a language, turn them into a project and return the documents and spans of it. - /// - /// Classes in the form of strings - /// The language the source code is in - /// A Tuple containing the Documents produced from the sources and their TextSpans if relevant - private static Document[] GetDocuments(string[] sources, string language) - { - if (language != LanguageNames.CSharp && language != LanguageNames.VisualBasic) - { - throw new ArgumentException("Unsupported Language"); - } - - var project = CreateProject(sources, language); - var documents = project.Documents.ToArray(); - - if (sources.Length != documents.Length) - { - throw new InvalidOperationException("Amount of sources did not match amount of Documents created"); - } - - return documents; - } - - /// - /// Create a Document from a string through creating a project that contains it. - /// - /// Classes in the form of a string - /// The language the source code is in - /// A Document created from the source string - protected static Document CreateDocument(string source, string language = LanguageNames.CSharp) - { - return CreateProject(new[] { source }, language).Documents.First(); - } - - /// - /// Create a project using the inputted strings as sources. - /// - /// Classes in the form of strings - /// The language the source code is in - /// A Project created out of the Documents created from the source strings - private static Project CreateProject(string[] sources, string language = LanguageNames.CSharp) - { - string fileNamePrefix = DefaultFilePathPrefix; - string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt; - - var projectId = ProjectId.CreateNewId(debugName: TestProjectName); - - var solution = new AdhocWorkspace() - .CurrentSolution - .AddProject(projectId, TestProjectName, TestProjectName, language) - .AddMetadataReference(projectId, CorlibReference) - .AddMetadataReference(projectId, SystemCoreReference) - .AddMetadataReference(projectId, CSharpSymbolsReference) - .AddMetadataReference(projectId, CodeAnalysisReference); - - int count = 0; - foreach (var source in sources) - { - var newFileName = fileNamePrefix + count + "." + fileExt; - var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); - solution = solution.AddDocument(documentId, newFileName, SourceText.From(source)); - count++; - } - return solution.GetProject(projectId); - } - #endregion - } -} - diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/DiagnosticVerifier.cs b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/DiagnosticVerifier.cs deleted file mode 100644 index 8ae0aa9c6..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/DiagnosticVerifier.cs +++ /dev/null @@ -1,270 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TestHelper -{ - /// - /// Superclass of all Unit Tests for DiagnosticAnalyzers - /// - public abstract partial class DiagnosticVerifier - { - #region To be implemented by Test classes - /// - /// Get the CSharp analyzer being tested - to be implemented in non-abstract class - /// - protected virtual DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() - { - return null; - } - - /// - /// Get the Visual Basic analyzer being tested (C#) - to be implemented in non-abstract class - /// - protected virtual DiagnosticAnalyzer GetBasicDiagnosticAnalyzer() - { - return null; - } - #endregion - - #region Verifier wrappers - - /// - /// Called to test a C# DiagnosticAnalyzer when applied on the single inputted string as a source - /// Note: input a DiagnosticResult for each Diagnostic expected - /// - /// A class in the form of a string to run the analyzer on - /// DiagnosticResults that should appear after the analyzer is run on the source - protected void VerifyCSharpDiagnostic(string source, params DiagnosticResult[] expected) - { - VerifyDiagnostics(new[] { source }, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); - } - - /// - /// Called to test a VB DiagnosticAnalyzer when applied on the single inputted string as a source - /// Note: input a DiagnosticResult for each Diagnostic expected - /// - /// A class in the form of a string to run the analyzer on - /// DiagnosticResults that should appear after the analyzer is run on the source - protected void VerifyBasicDiagnostic(string source, params DiagnosticResult[] expected) - { - VerifyDiagnostics(new[] { source }, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected); - } - - /// - /// Called to test a C# DiagnosticAnalyzer when applied on the inputted strings as a source - /// Note: input a DiagnosticResult for each Diagnostic expected - /// - /// An array of strings to create source documents from to run the analyzers on - /// DiagnosticResults that should appear after the analyzer is run on the sources - protected void VerifyCSharpDiagnostic(string[] sources, params DiagnosticResult[] expected) - { - VerifyDiagnostics(sources, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); - } - - /// - /// Called to test a VB DiagnosticAnalyzer when applied on the inputted strings as a source - /// Note: input a DiagnosticResult for each Diagnostic expected - /// - /// An array of strings to create source documents from to run the analyzers on - /// DiagnosticResults that should appear after the analyzer is run on the sources - protected void VerifyBasicDiagnostic(string[] sources, params DiagnosticResult[] expected) - { - VerifyDiagnostics(sources, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected); - } - - /// - /// General method that gets a collection of actual diagnostics found in the source after the analyzer is run, - /// then verifies each of them. - /// - /// An array of strings to create source documents from to run the analyzers on - /// The language of the classes represented by the source strings - /// The analyzer to be run on the source code - /// DiagnosticResults that should appear after the analyzer is run on the sources - private void VerifyDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expected) - { - var diagnostics = GetSortedDiagnostics(sources, language, analyzer); - VerifyDiagnosticResults(diagnostics, analyzer, expected); - } - - #endregion - - #region Actual comparisons and verifications - /// - /// Checks each of the actual Diagnostics found and compares them with the corresponding DiagnosticResult in the array of expected results. - /// Diagnostics are considered equal only if the DiagnosticResultLocation, Id, Severity, and Message of the DiagnosticResult match the actual diagnostic. - /// - /// The Diagnostics found by the compiler after running the analyzer on the source code - /// The analyzer that was being run on the sources - /// Diagnostic Results that should have appeared in the code - private static void VerifyDiagnosticResults(IEnumerable actualResults, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expectedResults) - { - int expectedCount = expectedResults.Count(); - int actualCount = actualResults.Count(); - - if (expectedCount != actualCount) - { - string diagnosticsOutput = actualResults.Any() ? FormatDiagnostics(analyzer, actualResults.ToArray()) : " NONE."; - - Assert.IsTrue(false, - string.Format("Mismatch between number of diagnostics returned, expected \"{0}\" actual \"{1}\"\r\n\r\nDiagnostics:\r\n{2}\r\n", expectedCount, actualCount, diagnosticsOutput)); - } - - for (int i = 0; i < expectedResults.Length; i++) - { - var actual = actualResults.ElementAt(i); - var expected = expectedResults[i]; - - if (expected.Line == -1 && expected.Column == -1) - { - if (actual.Location != Location.None) - { - Assert.IsTrue(false, - string.Format("Expected:\nA project diagnostic with No location\nActual:\n{0}", - FormatDiagnostics(analyzer, actual))); - } - } - else - { - VerifyDiagnosticLocation(analyzer, actual, actual.Location, expected.Locations.First()); - var additionalLocations = actual.AdditionalLocations.ToArray(); - - if (additionalLocations.Length != expected.Locations.Length - 1) - { - Assert.IsTrue(false, - string.Format("Expected {0} additional locations but got {1} for Diagnostic:\r\n {2}\r\n", - expected.Locations.Length - 1, additionalLocations.Length, - FormatDiagnostics(analyzer, actual))); - } - - for (int j = 0; j < additionalLocations.Length; ++j) - { - VerifyDiagnosticLocation(analyzer, actual, additionalLocations[j], expected.Locations[j + 1]); - } - } - - if (actual.Id != expected.Id) - { - Assert.IsTrue(false, - string.Format("Expected diagnostic id to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Id, actual.Id, FormatDiagnostics(analyzer, actual))); - } - - if (actual.Severity != expected.Severity) - { - Assert.IsTrue(false, - string.Format("Expected diagnostic severity to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Severity, actual.Severity, FormatDiagnostics(analyzer, actual))); - } - - if (actual.GetMessage() != expected.Message) - { - Assert.IsTrue(false, - string.Format("Expected diagnostic message to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Message, actual.GetMessage(), FormatDiagnostics(analyzer, actual))); - } - } - } - - /// - /// Helper method to VerifyDiagnosticResult that checks the location of a diagnostic and compares it with the location in the expected DiagnosticResult. - /// - /// The analyzer that was being run on the sources - /// The diagnostic that was found in the code - /// The Location of the Diagnostic found in the code - /// The DiagnosticResultLocation that should have been found - private static void VerifyDiagnosticLocation(DiagnosticAnalyzer analyzer, Diagnostic diagnostic, Location actual, DiagnosticResultLocation expected) - { - var actualSpan = actual.GetLineSpan(); - - Assert.IsTrue(actualSpan.Path == expected.Path || (actualSpan.Path != null && actualSpan.Path.Contains("Test0.") && expected.Path.Contains("Test.")), - string.Format("Expected diagnostic to be in file \"{0}\" was actually in file \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Path, actualSpan.Path, FormatDiagnostics(analyzer, diagnostic))); - - var actualLinePosition = actualSpan.StartLinePosition; - - // Only check line position if there is an actual line in the real diagnostic - if (actualLinePosition.Line > 0) - { - if (actualLinePosition.Line + 1 != expected.Line) - { - Assert.IsTrue(false, - string.Format("Expected diagnostic to be on line \"{0}\" was actually on line \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Line, actualLinePosition.Line + 1, FormatDiagnostics(analyzer, diagnostic))); - } - } - - // Only check column position if there is an actual column position in the real diagnostic - if (actualLinePosition.Character > 0) - { - if (actualLinePosition.Character + 1 != expected.Column) - { - Assert.IsTrue(false, - string.Format("Expected diagnostic to start at column \"{0}\" was actually at column \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Column, actualLinePosition.Character + 1, FormatDiagnostics(analyzer, diagnostic))); - } - } - } - #endregion - - #region Formatting Diagnostics - /// - /// Helper method to format a Diagnostic into an easily readable string - /// - /// The analyzer that this verifier tests - /// The Diagnostics to be formatted - /// The Diagnostics formatted as a string - private static string FormatDiagnostics(DiagnosticAnalyzer analyzer, params Diagnostic[] diagnostics) - { - var builder = new StringBuilder(); - for (int i = 0; i < diagnostics.Length; ++i) - { - builder.AppendLine("// " + diagnostics[i].ToString()); - - var analyzerType = analyzer.GetType(); - var rules = analyzer.SupportedDiagnostics; - - foreach (var rule in rules) - { - if (rule != null && rule.Id == diagnostics[i].Id) - { - var location = diagnostics[i].Location; - if (location == Location.None) - { - builder.AppendFormat("GetGlobalResult({0}.{1})", analyzerType.Name, rule.Id); - } - else - { - Assert.IsTrue(location.IsInSource, - $"Test base does not currently handle diagnostics in metadata locations. Diagnostic in metadata: {diagnostics[i]}\r\n"); - - string resultMethodName = diagnostics[i].Location.SourceTree.FilePath.EndsWith(".cs") ? "GetCSharpResultAt" : "GetBasicResultAt"; - var linePosition = diagnostics[i].Location.GetLineSpan().StartLinePosition; - - builder.AppendFormat("{0}({1}, {2}, {3}.{4})", - resultMethodName, - linePosition.Line + 1, - linePosition.Character + 1, - analyzerType.Name, - rule.Id); - } - - if (i != diagnostics.Length - 1) - { - builder.Append(','); - } - - builder.AppendLine(); - break; - } - } - } - return builder.ToString(); - } - #endregion - } -} diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/Test.csproj b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/Test.csproj deleted file mode 100644 index b4c378759..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/Test.csproj +++ /dev/null @@ -1,62 +0,0 @@ - - - - - Debug - AnyCPU - AnyCPU - 8.0.30703 - 2.0 - $guid1$ - Library - Properties - $safeprojectname$ - $safeprojectname$ - v$targetframeworkversion$ - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - $if$ ($targetframeworkversion$ >= 3.5) - - - - $endif$ - $if$ ($targetframeworkversion$ >= 4.0) - - $endif$ - - - false - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/Test.vstemplate b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/Test.vstemplate deleted file mode 100644 index 7870be126..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/Test.vstemplate +++ /dev/null @@ -1,42 +0,0 @@ - - - - TestProject - <No description available> - - CSharp - 1000 - bb967cab-2ca5-4dac-8809-65b2b28a6351 - true - TestProject - true - - - - AssemblyInfo.cs - UnitTests.cs - CodeFixVerifier.Helper.cs - DiagnosticResult.cs - DiagnosticVerifier.Helper.cs - CodeFixVerifier.cs - DiagnosticVerifier.cs - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKTestTemplateWizard - - - - - - - - - - - - - - - diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/UnitTests.cs b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/UnitTests.cs deleted file mode 100644 index 2fb1db9f2..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Test/UnitTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using TestHelper; -using $saferootprojectname$; - -namespace $safeprojectname$ -{ - [TestClass] - public class UnitTest : CodeFixVerifier - { - - //No diagnostics expected to show up - [TestMethod] - public void TestMethod1() - { - var test = @""; - - VerifyCSharpDiagnostic(test); - } - - //Diagnostic and CodeFix both triggered and checked for - [TestMethod] - public void TestMethod2() - { - var test = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - namespace ConsoleApplication1 - { - class TypeName - { - } - }"; - var expected = new DiagnosticResult - { - Id = "$saferootidentifiername$", - Message = String.Format("Type name '{0}' contains lowercase letters", "TypeName"), - Severity = DiagnosticSeverity.Warning, - Locations = - new[] { - new DiagnosticResultLocation("Test0.cs", 11, 15) - } - }; - - VerifyCSharpDiagnostic(test, expected); - - var fixtest = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - namespace ConsoleApplication1 - { - class TYPENAME - { - } - }"; - VerifyCSharpFix(test, fixtest); - } - - protected override CodeFixProvider GetCSharpCodeFixProvider() - { - return new $saferootidentifiername$CodeFixProvider(); - } - - protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() - { - return new $saferootidentifiername$Analyzer(); - } - } -} diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Vsix/Deployment.csproj b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Vsix/Deployment.csproj deleted file mode 100644 index 494897749..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Vsix/Deployment.csproj +++ /dev/null @@ -1,58 +0,0 @@ - - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - Debug - AnyCPU - AnyCPU - 2.0 - {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - $guid1$ - Library - Properties - $saferootprojectname$.Vsix - $saferootprojectname$.Vsix - v$targetframeworkversion$ - false - false - false - false - false - false - Roslyn - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - Program - $(DevEnvDir)devenv.exe - /rootsuffix Roslyn - - - - Designer - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Vsix/Deployment.vstemplate b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Vsix/Deployment.vstemplate deleted file mode 100644 index c22f9db9d..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Vsix/Deployment.vstemplate +++ /dev/null @@ -1,23 +0,0 @@ - - - - Vsix - <No description available> - - CSharp - 1000 - bb967cab-2ca5-4dac-8809-65b2b28a6350 - true - Vsix - true - - - - source.extension.vsixmanifest - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKVsixTemplateWizardThirdProject - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Vsix/source.extension.vsixmanifest b/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Vsix/source.extension.vsixmanifest deleted file mode 100644 index d3bf7b103..000000000 --- a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Vsix/source.extension.vsixmanifest +++ /dev/null @@ -1,22 +0,0 @@ - - - - - $saferootprojectname$ - This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn"). - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/AssemblyInfo.vb b/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/AssemblyInfo.vb deleted file mode 100644 index 9bf1ac4ce..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/AssemblyInfo.vb +++ /dev/null @@ -1,29 +0,0 @@ -Imports System.Reflection -Imports System.Runtime.InteropServices - -' General Information about an assembly is controlled through the following -' set of attributes. Change these attribute values to modify the information -' associated with an assembly. - -' Review the values of the assembly attributes - - - - - - - - - - -' Version information for an assembly consists of the following four values: -' -' Major Version -' Minor Version -' Build Number -' Revision -' -' You can specify all the values or you can default the Build and Revision Numbers -' by using the '*' as shown below: - - diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/CodeRefactoring.vbproj b/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/CodeRefactoring.vbproj deleted file mode 100644 index 6222e9554..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/CodeRefactoring.vbproj +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - 11.0 - Debug - AnyCPU - AnyCPU - 2.0 - {14182A97-F7F0-4C62-8B27-98AA8AE2109A};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} - $guid1$ - Library - $safeprojectname$ - $safeprojectname$ - Profile7 - v4.5 - 512 - - - true - full - false - bin\Debug\ - true - true - prompt - $(NoWarn);41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 - - - pdbonly - true - bin\Release\ - false - true - prompt - $(NoWarn);41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 - - - On - - - Binary - - - Off - - - On - - - Program - $(DevEnvDir)devenv.exe - /rootsuffix Roslyn - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/CodeRefactoringProvider.vb b/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/CodeRefactoringProvider.vb deleted file mode 100644 index 59d5db243..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/CodeRefactoringProvider.vb +++ /dev/null @@ -1,43 +0,0 @@ - -Friend Class $saferootidentifiername$CodeRefactoringProvider - Inherits CodeRefactoringProvider - - Public NotOverridable Overrides Async Function ComputeRefactoringsAsync(context As CodeRefactoringContext) As Task - ' TODO: Replace the following code with your own analysis, generating a CodeAction for each refactoring to offer - - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) - - ' Find the node at the selection. - Dim node = root.FindNode(context.Span) - - ' Only offer a refactoring if the selected node is a type statement node. - Dim typeDecl = TryCast(node, TypeStatementSyntax) - If typeDecl Is Nothing Then - Return - End If - - ' For any type statement node, create a code action to reverse the identifier text. - Dim action = CodeAction.Create("Reverse type name", Function(c) ReverseTypeNameAsync(context.Document, typeDecl, c)) - - ' Register this code action. - context.RegisterRefactoring(action) - End Function - - Private Async Function ReverseTypeNameAsync(document As Document, typeStmt As TypeStatementSyntax, cancellationToken As CancellationToken) As Task(Of Solution) - ' Produce a reversed version of the type statement's identifier token. - Dim identifierToken = typeStmt.Identifier - Dim newName = New String(identifierToken.Text.ToCharArray.Reverse.ToArray) - - ' Get the symbol representing the type to be renamed. - Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken) - Dim typeSymbol = semanticModel.GetDeclaredSymbol(typeStmt, cancellationToken) - - ' Produce a new solution that has all references to that type renamed, including the declaration. - Dim originalSolution = document.Project.Solution - Dim optionSet = originalSolution.Workspace.Options - Dim newSolution = Await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(False) - - ' Return the new solution with the now-uppercase type name. - Return newSolution - End Function -End Class diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/VBCodeRefactoring.vstemplate b/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/VBCodeRefactoring.vstemplate deleted file mode 100644 index 20fbe43dd..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/VBCodeRefactoring.vstemplate +++ /dev/null @@ -1,38 +0,0 @@ - - - Code Refactoring (VSIX) - Create a Visual Basic refactoring, deployed as a VSIX extension - VisualBasic - - 1000 - true - CodeRefactoring - true - Enabled - true - true - - - - CodeRefactoringProvider.vb - AssemblyInfo.vb - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKAnalyzerTemplateWizard - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/Deployment.vbproj b/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/Deployment.vbproj deleted file mode 100644 index 16568a9b6..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/Deployment.vbproj +++ /dev/null @@ -1,79 +0,0 @@ - - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - Debug - AnyCPU - AnyCPU - 2.0 - {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} - $guid1$ - Library - $saferootprojectname$.Vsix - $saferootprojectname$.Vsix - v$targetframeworkversion$ - false - false - false - false - false - false - Roslyn - - - true - full - false - bin\Debug\ - true - true - prompt - $(NoWarn);41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 - - - pdbonly - true - bin\Release\ - false - true - prompt - $(NoWarn);41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 - - - On - - - Binary - - - Off - - - On - - - Program - $(DevEnvDir)devenv.exe - /rootsuffix Roslyn - - - - Designer - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/Deployment.vstemplate b/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/Deployment.vstemplate deleted file mode 100644 index 656b45444..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/Deployment.vstemplate +++ /dev/null @@ -1,24 +0,0 @@ - - - - Vsix - <No description available> - - VisualBasic - 1000 - bb967cab-2ca5-4dac-8809-65b2b28a6350 - true - Vsix - true - - - - source.extension.vsixmanifest - AssemblyInfo.vb - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKVsixTemplateWizardSecondProject - - diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/source.extension.vsixmanifest b/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/source.extension.vsixmanifest deleted file mode 100644 index 279da4ccf..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/source.extension.vsixmanifest +++ /dev/null @@ -1,21 +0,0 @@ - - - - - $saferootprojectname$ - This is a sample code refactoring extension for the .NET Compiler Platform ("Roslyn"). - - - - - - - - - - - - - - - diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/ConsoleApplication.vbproj b/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/ConsoleApplication.vbproj deleted file mode 100644 index efe12e8e5..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/ConsoleApplication.vbproj +++ /dev/null @@ -1,114 +0,0 @@ - - - - - Debug - x86 - x86 - - - - - {$guid2$} - Exe - $safeprojectname$.Module1 - $safeprojectname$ - $safeprojectname$ - 512 - Console - v$targetframeworkversion$ - - - true - full - true - true - bin\Debug\ - $safeprojectname$.xml - $(NoWarn);42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 - - - pdbonly - false - true - true - bin\Release\ - $safeprojectname$.xml - $(NoWarn);42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - Application.myapp - - - - True - True - Resources.resx - - - True - Settings.settings - True - - - - - VbMyResourcesResXFileCodeGenerator - Resources.Designer.vb - My.Resources - Designer - - - - - MyApplicationCodeGenerator - Application.Designer.vb - - - SettingsSingleFileGenerator - My - Settings.Designer.vb - - - - - - diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/Module1.vb b/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/Module1.vb deleted file mode 100644 index 762248a18..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/Module1.vb +++ /dev/null @@ -1,7 +0,0 @@ -Module $safeprojectname$ - - Sub Main() - - End Sub - -End Module diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/MyApplication.Designer.vb b/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/MyApplication.Designer.vb deleted file mode 100644 index efb226e8c..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/MyApplication.Designer.vb +++ /dev/null @@ -1,13 +0,0 @@ -'------------------------------------------------------------------------------ -' -' This code was generated by a tool. -' Runtime Version:$clrversion$ -' -' Changes to this file may cause incorrect behavior and will be lost if -' the code is regenerated. -' -'------------------------------------------------------------------------------ - -Option Strict On -Option Explicit On - diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/MyApplication.myapp b/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/MyApplication.myapp deleted file mode 100644 index e62f1a533..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/MyApplication.myapp +++ /dev/null @@ -1,10 +0,0 @@ - - - false - false - 0 - true - 0 - 2 - true - diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/Resources.Designer.vb b/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/Resources.Designer.vb deleted file mode 100644 index 262cbf316..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/Resources.Designer.vb +++ /dev/null @@ -1,62 +0,0 @@ -'------------------------------------------------------------------------------ -' -' This code was generated by a tool. -' Runtime Version:$clrversion$ -' -' Changes to this file may cause incorrect behavior and will be lost if -' the code is regenerated. -' -'------------------------------------------------------------------------------ - -Option Strict On -Option Explicit On - - -Namespace My.Resources - - 'This class was auto-generated by the StronglyTypedResourceBuilder - 'class via a tool like ResGen or Visual Studio. - 'To add or remove a member, edit your .ResX file then rerun ResGen - 'with the /str option, or rebuild your VS project. - ''' - ''' A strongly-typed resource class, for looking up localized strings, etc. - ''' - _ - Friend Module Resources - - Private resourceMan As Global.System.Resources.ResourceManager - - Private resourceCulture As Global.System.Globalization.CultureInfo - - ''' - ''' Returns the cached ResourceManager instance used by this class. - ''' - _ - Friend ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager - Get - If Object.ReferenceEquals(resourceMan, Nothing) Then - Dim temp As Global.System.Resources.ResourceManager = New Global.System.Resources.ResourceManager("$safeprojectname$.Resources", GetType(Resources).Assembly) - resourceMan = temp - End If - Return resourceMan - End Get - End Property - - ''' - ''' Overrides the current thread's CurrentUICulture property for all - ''' resource lookups using this strongly typed resource class. - ''' - _ - Friend Property Culture() As Global.System.Globalization.CultureInfo - Get - Return resourceCulture - End Get - Set(ByVal value As Global.System.Globalization.CultureInfo) - resourceCulture = value - End Set - End Property - End Module -End Namespace diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/Settings.Designer.vb b/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/Settings.Designer.vb deleted file mode 100644 index b356aa8ea..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/Settings.Designer.vb +++ /dev/null @@ -1,73 +0,0 @@ -'------------------------------------------------------------------------------ -' -' This code was generated by a tool. -' Runtime Version:$clrversion$ -' -' Changes to this file may cause incorrect behavior and will be lost if -' the code is regenerated. -' -'------------------------------------------------------------------------------ - -Option Strict On -Option Explicit On - - -Namespace My - - _ - Partial Friend NotInheritable Class MySettings - Inherits Global.System.Configuration.ApplicationSettingsBase - - Private Shared defaultInstance As MySettings = CType(Global.System.Configuration.ApplicationSettingsBase.Synchronized(New MySettings), MySettings) - -#Region "My.Settings Auto-Save Functionality" -#If _MyType = "WindowsForms" Then - Private Shared addedHandler As Boolean - - Private Shared addedHandlerLockObject As New Object - - _ - Private Shared Sub AutoSaveSettings(ByVal sender As Global.System.Object, ByVal e As Global.System.EventArgs) - If My.Application.SaveMySettingsOnExit Then - My.Settings.Save() - End If - End Sub -#End If -#End Region - - Public Shared ReadOnly Property [Default]() As MySettings - Get - -#If _MyType = "WindowsForms" Then - If Not addedHandler Then - SyncLock addedHandlerLockObject - If Not addedHandler Then - AddHandler My.Application.Shutdown, AddressOf AutoSaveSettings - addedHandler = True - End If - End SyncLock - End If -#End If - Return defaultInstance - End Get - End Property - End Class -End Namespace - -Namespace My - - _ - Friend Module MySettingsProperty - - _ - Friend ReadOnly Property Settings() As Global.$safeprojectname$.My.MySettings - Get - Return Global.$safeprojectname$.My.MySettings.Default - End Get - End Property - End Module -End Namespace diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/Settings.settings b/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/Settings.settings deleted file mode 100644 index 85b890b3c..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/VBConsoleApplication.vstemplate b/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/VBConsoleApplication.vstemplate deleted file mode 100644 index cca9577c5..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/VBConsoleApplication.vstemplate +++ /dev/null @@ -1,45 +0,0 @@ - - - Stand-Alone Code Analysis Tool (Portable Class Library) - Create a code analysis command-line application - VisualBasic - - 950 - true - ConsoleApplication - true - Enabled - true - CodeAnalysisConsole.ico - true - - - - Module1.vb - AssemblyInfo.vb - MyApplication.myapp - MyApplication.Designer.vb - Resources.resx - Resources.Designer.vb - Settings.settings - Settings.Designer.vb - - - - NuGet.VisualStudio.Interop, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - NuGet.VisualStudio.TemplateWizard - - - - - - - - - - - - - - diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/AssemblyInfo.vb b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/AssemblyInfo.vb deleted file mode 100644 index 267099513..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/AssemblyInfo.vb +++ /dev/null @@ -1,29 +0,0 @@ -Imports System.Reflection -Imports System.Runtime.InteropServices - -' General Information about an assembly is controlled through the following -' set of attributes. Change these attribute values to modify the information -' associated with an assembly. - -' Review the values of the assembly attributes - - - - - - - - - - -' Version information for an assembly consists of the following four values: -' -' Major Version -' Minor Version -' Build Number -' Revision -' -' You can specify all the values or you can default the Build and Revision Numbers -' by using the '*' as shown below: - - diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/CodeFixProvider.vb b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/CodeFixProvider.vb deleted file mode 100644 index 1190a96a2..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/CodeFixProvider.vb +++ /dev/null @@ -1,58 +0,0 @@ -Imports System.Collections.Immutable -Imports Microsoft.CodeAnalysis.Rename - - -Public Class $saferootidentifiername$CodeFixProvider - Inherits CodeFixProvider - - Private Const title As String = "Make uppercase" - - Public NotOverridable Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) - Get - Return ImmutableArray.Create($saferootidentifiername$Analyzer.DiagnosticId) - End Get - End Property - - Public NotOverridable Overrides Function GetFixAllProvider() As FixAllProvider - ' See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers - Return WellKnownFixAllProviders.BatchFixer - End Function - - Public NotOverridable Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) - - ' TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest - - Dim diagnostic = context.Diagnostics.First() - Dim diagnosticSpan = diagnostic.Location.SourceSpan - - ' Find the type statement identified by the diagnostic. - Dim declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType(Of TypeStatementSyntax)().First() - - ' Register a code action that will invoke the fix. - context.RegisterCodeFix( - CodeAction.Create( - title:=title, - createChangedSolution:=Function(c) MakeUppercaseAsync(context.Document, declaration, c), - equivalenceKey:=title), - diagnostic) - End Function - - Private Async Function MakeUppercaseAsync(document As Document, typeStmt As TypeStatementSyntax, cancellationToken As CancellationToken) As Task(Of Solution) - ' Compute new uppercase name. - Dim identifierToken = typeStmt.Identifier - Dim newName = identifierToken.Text.ToUpperInvariant() - - ' Get the symbol representing the type to be renamed. - Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken) - Dim typeSymbol = semanticModel.GetDeclaredSymbol(typeStmt, cancellationToken) - - ' Produce a new solution that has all references to that type renamed, including the declaration. - Dim originalSolution = document.Project.Solution - Dim optionSet = originalSolution.Workspace.Options - Dim newSolution = Await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(False) - - ' Return the new solution with the now-uppercase type name. - Return newSolution - End Function -End Class diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Diagnostic.nuspec b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Diagnostic.nuspec deleted file mode 100644 index dbfd77b0b..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Diagnostic.nuspec +++ /dev/null @@ -1,27 +0,0 @@ - - - - $saferootprojectname$ - 1.0.0.0 - $saferootprojectname$ - $username$ - $username$ - http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE - http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE - http://ICON_URL_HERE_OR_DELETE_THIS_LINE - false - $saferootprojectname$ - Summary of changes made in this release of the package. - Copyright - $saferootprojectname$, analyzers - - - - true - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Diagnostic.vbproj b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Diagnostic.vbproj deleted file mode 100644 index 144347c2d..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Diagnostic.vbproj +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - 11.0 - Debug - AnyCPU - AnyCPU - 2.0 - {14182A97-F7F0-4C62-8B27-98AA8AE2109A};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} - $guid1$ - Library - $saferootprojectname$ - $safeprojectname$ - Profile7 - v4.5 - 512 - false - - - true - full - false - bin\Debug\ - true - true - prompt - $(NoWarn);41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 - - - pdbonly - true - bin\Release\ - false - true - prompt - $(NoWarn);41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 - - - On - - - Binary - - - Off - - - On - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - VbMyResourcesResXFileCodeGenerator - Resources.Designer.vb - My.Resources - Designer - - - - - Designer - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/DiagnosticAnalyzer.vb b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/DiagnosticAnalyzer.vb deleted file mode 100644 index e320c2c07..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/DiagnosticAnalyzer.vb +++ /dev/null @@ -1,41 +0,0 @@ - -Public Class $saferootidentifiername$Analyzer - Inherits DiagnosticAnalyzer - - Public Const DiagnosticId = "$saferootidentifiername$" - - ' You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat. - ' See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Localizing%20Analyzers.md for more on localization - Private Shared ReadOnly Title As LocalizableString = New LocalizableResourceString(NameOf(My.Resources.AnalyzerTitle), My.Resources.ResourceManager, GetType(My.Resources.Resources)) - Private Shared ReadOnly MessageFormat As LocalizableString = New LocalizableResourceString(NameOf(My.Resources.AnalyzerMessageFormat), My.Resources.ResourceManager, GetType(My.Resources.Resources)) - Private Shared ReadOnly Description As LocalizableString = New LocalizableResourceString(NameOf(My.Resources.AnalyzerDescription), My.Resources.ResourceManager, GetType(My.Resources.Resources)) - Private Const Category = "Naming" - - Private Shared Rule As New DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault:=True, description:=Description) - - Public Overrides ReadOnly Property SupportedDiagnostics As ImmutableArray(Of DiagnosticDescriptor) - Get - Return ImmutableArray.Create(Rule) - End Get - End Property - - Public Overrides Sub Initialize(context As AnalysisContext) - ' TODO: Consider registering other actions that act on syntax instead of or in addition to symbols - ' See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Analyzer%20Actions%20Semantics.md for more information - context.RegisterSymbolAction(AddressOf AnalyzeSymbol, SymbolKind.NamedType) - End Sub - - Private Sub AnalyzeSymbol(context As SymbolAnalysisContext) - ' TODO: Replace the following code with your own analysis, generating Diagnostic objects for any issues you find - - Dim namedTypeSymbol = CType(context.Symbol, INamedTypeSymbol) - - ' Find just those named type symbols with names containing lowercase letters. - If namedTypeSymbol.Name.ToCharArray.Any(AddressOf Char.IsLower) Then - ' For all such symbols, produce a diagnostic. - Dim diag = Diagnostic.Create(Rule, namedTypeSymbol.Locations(0), namedTypeSymbol.Name) - - context.ReportDiagnostic(diag) - End If - End Sub -End Class diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/DiagnosticAnalyzer.vstemplate b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/DiagnosticAnalyzer.vstemplate deleted file mode 100644 index 551134edd..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/DiagnosticAnalyzer.vstemplate +++ /dev/null @@ -1,45 +0,0 @@ - - - - DiagnosticAnalyzer - <No description available> - - CSharp - - 1000 - bb967cab-2ca5-4dac-8809-65b2b28a6350 - true - DiagnosticAnalyzer - true - - - - DiagnosticAnalyzer.vb - CodeFixProvider.vb - AssemblyInfo.vb - Diagnostic.nuspec - Resources.resx - Resources.Designer.vb - install.ps1 - uninstall.ps1 - ReadMe.txt - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKAnalyzerTemplateWizard - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/ReadMe.txt b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/ReadMe.txt deleted file mode 100644 index bfc614ce1..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/ReadMe.txt +++ /dev/null @@ -1,33 +0,0 @@ - -Building this project will produce an analyzer .dll, as well as the -following two ways you may wish to package that analyzer: - * A NuGet package (.nupkg file) that will add your assembly as a - project-local analyzer that participates in builds. - * A VSIX extension (.vsix file) that will apply your analyzer to all projects - and works just in the IDE. - -To debug your analyzer, make sure the default project is the VSIX project and -start debugging. This will deploy the analyzer as a VSIX into another instance -of Visual Studio, which is useful for debugging, even if you intend to produce -a NuGet package. - - -TRYING OUT YOUR NUGET PACKAGE - -To try out the NuGet package: - 1. Create a local NuGet feed by following the instructions here: - > http://docs.nuget.org/docs/creating-packages/hosting-your-own-nuget-feeds - 2. Copy the .nupkg file into that folder. - 3. Open the target project in Visual Studio 2015. - 4. Right-click on the project node in Solution Explorer and choose Manage - NuGet Packages. - 5. Select the NuGet feed you created on the left. - 6. Choose your analyzer from the list and click Install. - -If you want to automatically deploy the .nupkg file to the local feed folder -when you build this project, follow these steps: - 1. Right-click on this project in Solution Explorer and choose 'Unload Project'. - 2. Right-click on this project and click "Edit". - 3. Scroll down to the "AfterBuild" target. - 4. In the "Exec" task, change the value inside "Command" after the -OutputDirectory - path to point to your local NuGet feed folder. diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/source.extension.vsixmanifest b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/source.extension.vsixmanifest deleted file mode 100644 index 8fea5b291..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/source.extension.vsixmanifest +++ /dev/null @@ -1,25 +0,0 @@ - - - - - $safeprojectname$ - This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn"). - - - - - - - - - - - - - - - - - - - diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Application.Designer.vb b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Application.Designer.vb deleted file mode 100644 index ecc48053e..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Application.Designer.vb +++ /dev/null @@ -1,13 +0,0 @@ -'------------------------------------------------------------------------------ -' -' This code was generated by a tool. -' Runtime Version:4.0.30319.35317 -' -' Changes to this file may cause incorrect behavior and will be lost if -' the code is regenerated. -' -'------------------------------------------------------------------------------ - -Option Strict On -Option Explicit On - diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Application.myapp b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Application.myapp deleted file mode 100644 index 758895def..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Application.myapp +++ /dev/null @@ -1,10 +0,0 @@ - - - false - false - 0 - true - 0 - 1 - true - diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/AssemblyInfo.vb b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/AssemblyInfo.vb deleted file mode 100644 index 7a4498431..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/AssemblyInfo.vb +++ /dev/null @@ -1,35 +0,0 @@ -Imports System -Imports System.Reflection -Imports System.Runtime.InteropServices - -' General Information about an assembly is controlled through the following -' set of attributes. Change these attribute values to modify the information -' associated with an assembly. - -' Review the values of the assembly attributes - - - - - - - - - - -'The following GUID is for the ID of the typelib if this project is exposed to COM - - -' Version information for an assembly consists of the following four values: -' -' Major Version -' Minor Version -' Build Number -' Revision -' -' You can specify all the values or you can default the Build and Revision Numbers -' by using the '*' as shown below: -' - - - diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/CodeFixVerifier.vb b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/CodeFixVerifier.vb deleted file mode 100644 index 3c782bcf5..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/CodeFixVerifier.vb +++ /dev/null @@ -1,117 +0,0 @@ -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.CodeActions -Imports Microsoft.CodeAnalysis.CodeFixes -Imports Microsoft.CodeAnalysis.Diagnostics -Imports Microsoft.CodeAnalysis.Formatting -Imports Microsoft.VisualStudio.TestTools.UnitTesting -Imports System.Collections.Generic -Imports System.Threading - -Namespace TestHelper - ''' - ''' Superclass of all Unit tests made for diagnostics with codefixes. - ''' Contains methods used to verify correctness of codefixes - ''' - Partial Public MustInherit Class CodeFixVerifier - Inherits DiagnosticVerifier - ''' - ''' Returns the codefix being tested (C#) - to be implemented in non-abstract class - ''' - ''' The CodeFixProvider to be used for CSharp code - Protected Overridable Function GetCSharpCodeFixProvider() As CodeFixProvider - Return Nothing - End Function - - ''' - ''' Returns the codefix being tested (VB) - to be implemented in non-abstract class - ''' - ''' The CodeFixProvider to be used for VisualBasic code - Protected Overridable Function GetBasicCodeFixProvider() As CodeFixProvider - Return Nothing - End Function - - ''' - ''' Called to test a C# codefix when applied on the inputted string as a source - ''' - ''' A class in the form of a string before the CodeFix was applied to it - ''' A class in the form of a string after the CodeFix was applied to it - ''' Index determining which codefix to apply if there are multiple - ''' A bool controlling whether Or Not the test will fail if the CodeFix introduces other warnings after being applied - Protected Sub VerifyCSharpFix(oldSource As String, newSource As String, Optional codeFixIndex As Integer? = Nothing, Optional allowNewCompilerDiagnostics As Boolean = False) - VerifyFix(LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), GetCSharpCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics) - End Sub - - ''' - ''' Called to test a VB codefix when applied on the inputted string as a source - ''' - ''' A class in the form of a string before the CodeFix was applied to it - ''' A class in the form of a string after the CodeFix was applied to it - ''' Index determining which codefix to apply if there are multiple - ''' A bool controlling whether Or Not the test will fail if the CodeFix introduces other warnings after being applied - Protected Sub VerifyBasicFix(oldSource As String, newSource As String, Optional codeFixIndex As Integer? = Nothing, Optional allowNewCompilerDiagnostics As Boolean = False) - VerifyFix(LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), GetBasicCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics) - End Sub - - ''' - ''' General verifier for codefixes. - ''' Creates a Document from the source string, then gets diagnostics on it And applies the relevant codefixes. - ''' Then gets the string after the codefix Is applied And compares it with the expected result. - ''' Note: If any codefix causes New diagnostics To show up, the test fails unless allowNewCompilerDiagnostics Is Set To True. - ''' - ''' The language the source code Is in - ''' The analyzer to be applied to the source code - ''' The codefix to be applied to the code wherever the relevant Diagnostic Is found - ''' A class in the form of a string before the CodeFix was applied to it - ''' A class in the form of a string after the CodeFix was applied to it - ''' Index determining which codefix to apply if there are multiple - ''' A bool controlling whether Or Not the test will fail if the CodeFix introduces other warnings after being applied - Private Sub VerifyFix(language As String, analyzer As DiagnosticAnalyzer, codeFixProvider As CodeFixProvider, oldSource As String, newSource As String, codeFixIndex As Integer?, allowNewCompilerDiagnostics As Boolean) - - Dim document = CreateDocument(oldSource, language) - Dim analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, New document() {document}) - Dim compilerDiagnostics = GetCompilerDiagnostics(document) - Dim attempts = analyzerDiagnostics.Length - - For i = 0 To attempts - 1 - Dim actions = New List(Of CodeAction)() - Dim context = New CodeFixContext(document, analyzerDiagnostics(0), Sub(a, d) actions.Add(a), CancellationToken.None) - codeFixProvider.RegisterCodeFixesAsync(context).Wait() - - If Not actions.Any() Then - Exit For - End If - - If (codeFixIndex IsNot Nothing) Then - document = ApplyFix(document, actions.ElementAt(codeFixIndex.Value)) - Exit For - End If - - document = ApplyFix(document, actions.ElementAt(0)) - analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, New document() {document}) - - Dim newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)) - - 'check if applying the code fix introduced any New compiler diagnostics - If Not allowNewCompilerDiagnostics AndAlso newCompilerDiagnostics.Any() Then - ' Format And get the compiler diagnostics again so that the locations make sense in the output - document = document.WithSyntaxRoot(Formatter.Format(document.GetSyntaxRootAsync().Result, Formatter.Annotation, document.Project.Solution.Workspace)) - newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)) - - Assert.IsTrue(False, - String.Format("Fix introduced new compiler diagnostics:{2}{0}{2}{2}New document:{2}{1}{2}", - String.Join(vbNewLine, newCompilerDiagnostics.Select(Function(d) d.ToString())), - document.GetSyntaxRootAsync().Result.ToFullString(), vbNewLine)) - End If - - 'check if there are analyzer diagnostics left after the code fix - If Not analyzerDiagnostics.Any() Then - Exit For - End If - Next - - 'after applying all of the code fixes, compare the resulting string to the inputted one - Dim actual = GetStringFromDocument(document) - Assert.AreEqual(newSource, actual) - End Sub - End Class -End Namespace diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticResult.vb b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticResult.vb deleted file mode 100644 index 8e65996df..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticResult.vb +++ /dev/null @@ -1,83 +0,0 @@ -Imports Microsoft.CodeAnalysis - -Namespace TestHelper - - ''' - ''' Location where the diagnostic appears, as determined by path, line number, And column number. - ''' - Public Structure DiagnosticResultLocation - - Public Sub New(path As String, line As Integer, column As Integer) - If line < -1 Then - Throw New ArgumentOutOfRangeException(NameOf(line), "line must be >= -1") - End If - - If column < -1 Then - Throw New ArgumentOutOfRangeException(NameOf(column), "column must be >= -1") - End If - - Me.Path = path - Me.Line = line - Me.Column = column - - End Sub - - - Public Property Path As String - Public Property Line As Integer - Public Property Column As Integer - - End Structure - - - ''' - ''' Struct that stores information about a Diagnostic appearing in a source - ''' - Public Structure DiagnosticResult - - Private innerlocations As DiagnosticResultLocation() - - Public Property Locations As DiagnosticResultLocation() - Get - - If Me.innerlocations Is Nothing Then - Me.innerlocations = {} - End If - - Return Me.innerlocations - End Get - - Set - - Me.innerlocations = Value - End Set - End Property - - Public Property Severity As DiagnosticSeverity - - Public Property Id As String - - Public Property Message As String - - Public ReadOnly Property Path As String - Get - Return If(Me.Locations.Length > 0, Me.Locations(0).Path, "") - End Get - End Property - - Public ReadOnly Property Line As Integer - Get - Return If(Me.Locations.Length > 0, Me.Locations(0).Line, -1) - End Get - End Property - - Public ReadOnly Property Column As Integer - Get - Return If(Me.Locations.Length > 0, Me.Locations(0).Column, -1) - End Get - End Property - - End Structure - -End Namespace - diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticVerifier.vb b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticVerifier.vb deleted file mode 100644 index d9e7a2221..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticVerifier.vb +++ /dev/null @@ -1,303 +0,0 @@ -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Diagnostics -Imports Microsoft.VisualStudio.TestTools.UnitTesting -Imports System.Text - -Namespace TestHelper - ''' Superclass of all Unit Tests for DiagnosticAnalyzers. - Partial Public MustInherit Class DiagnosticVerifier -#Region " To be implemented by Test classes " - ''' - ''' Get the CSharp analyzer being tested - to be implemented in non-abstract class - ''' - Protected Overridable Function GetCSharpDiagnosticAnalyzer() As DiagnosticAnalyzer - Return Nothing - End Function - - ''' - ''' Get the Visual Basic analyzer being tested (C#) - to be implemented in non-abstract class - ''' - Protected Overridable Function GetBasicDiagnosticAnalyzer() As DiagnosticAnalyzer - Return Nothing - End Function -#End Region - -#Region " Verifier wrappers " - - ''' - ''' Called to test a C# DiagnosticAnalyzer when applied on the single inputted string as a source - ''' Note: input a DiagnosticResult For Each Diagnostic expected - ''' - ''' A class in the form of a string to run the analyzer on - ''' DiagnosticResults that should appear after the analyzer Is run on the source - Protected Sub VerifyCSharpDiagnostic(source As String, ParamArray expected As DiagnosticResult()) - - VerifyDiagnostics({source}, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected) - End Sub - - ''' - ''' Called to test a VB DiagnosticAnalyzer when applied on the single inputted string as a source - ''' Note: input a DiagnosticResult For Each Diagnostic expected - ''' - ''' A class in the form of a string to run the analyzer on - ''' DiagnosticResults that should appear after the analyzer Is run on the source - Protected Sub VerifyBasicDiagnostic(source As String, ParamArray expected As DiagnosticResult()) - - VerifyDiagnostics({source}, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected) - End Sub - - ''' - ''' Called to test a C# DiagnosticAnalyzer when applied on the inputted strings as a source - ''' Note: input a DiagnosticResult For Each Diagnostic expected - ''' - ''' An array of strings to create source documents from to run the analyzers on - ''' DiagnosticResults that should appear after the analyzer Is run on the sources - Protected Sub VerifyCSharpDiagnostic(sources As String(), ParamArray expected As DiagnosticResult()) - - VerifyDiagnostics(sources, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected) - End Sub - - ''' - ''' Called to test a VB DiagnosticAnalyzer when applied on the inputted strings as a source - ''' Note: input a DiagnosticResult For Each Diagnostic expected - ''' - ''' An array of strings to create source documents from to run the analyzers on - ''' DiagnosticResults that should appear after the analyzer Is run on the sources - Protected Sub VerifyBasicDiagnostic(sources As String(), ParamArray expected As DiagnosticResult()) - - VerifyDiagnostics(sources, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected) - End Sub - - ''' - ''' General method that gets a collection of actual diagnostics found in the source after the analyzer Is run, - ''' then verifies each of them. - ''' - ''' An array of strings to create source documents from to run the analyzers on - ''' The language of the classes represented by the source strings - ''' The analyzer to be run on the source code - ''' DiagnosticResults that should appear after the analyzer Is run on the sources - Private Sub VerifyDiagnostics(sources As String(), language As String, analyzer As DiagnosticAnalyzer, ParamArray expected As DiagnosticResult()) - - Dim diagnostics = GetSortedDiagnostics(sources, language, analyzer) - VerifyDiagnosticResults(diagnostics, analyzer, expected) - End Sub - -#End Region - -#Region " Actual comparisons And verifications " - ''' - ''' Checks each of the actual Diagnostics found And compares them with the corresponding DiagnosticResult in the array of expected results. - ''' Diagnostics are considered equal only if the DiagnosticResultLocation, Id, Severity, And Message of the DiagnosticResult match the actual diagnostic. - ''' - ''' The Diagnostics found by the compiler after running the analyzer on the source code - ''' The analyzer that was being run on the sources - ''' Diagnostic Results that should have appeared in the code - Private Shared Sub VerifyDiagnosticResults(actualResults As IEnumerable(Of Diagnostic), analyzer As DiagnosticAnalyzer, ParamArray expectedResults As DiagnosticResult()) - - Dim expectedCount = expectedResults.Count() - Dim actualCount = actualResults.Count() - - If expectedCount <> actualCount Then - - Dim diagnosticsOutput = If(actualResults.Any(), FormatDiagnostics(analyzer, actualResults.ToArray()), " NONE.") - - Assert.IsTrue(False, - String.Format( -"Mismatch between number of diagnostics returned, expected ""{0}"" actual ""{1}"" - -Diagnostics: -{2} -", expectedCount, actualCount, diagnosticsOutput)) - End If - - For i = 0 To expectedResults.Length - 1 - - Dim actual = actualResults.ElementAt(i) - Dim expected = expectedResults(i) - - If expected.Line = -1 AndAlso expected.Column = -1 Then - - If (actual.Location <> Location.None) Then - - Assert.IsTrue(False, - String.Format( -"Expected: -A project diagnostic with No location -Actual: -{0}", - FormatDiagnostics(analyzer, actual))) - End If - - Else - - VerifyDiagnosticLocation(analyzer, actual, actual.Location, expected.Locations.First()) - Dim additionalLocations = actual.AdditionalLocations.ToArray() - - If (additionalLocations.Length <> expected.Locations.Length - 1) Then - - Assert.IsTrue(False, - String.Format( -"Expected {0} additional locations but got {1} for Diagnostic: - {2} -", - expected.Locations.Length - 1, additionalLocations.Length, - FormatDiagnostics(analyzer, actual))) - End If - - For j = 0 To additionalLocations.Length - 1 - - VerifyDiagnosticLocation(analyzer, actual, additionalLocations(j), expected.Locations(j + 1)) - Next - End If - - If (actual.Id <> expected.Id) Then - - Assert.IsTrue(False, - String.Format( -"Expected diagnostic id to be ""{0}"" was ""{1}"" - -Diagnostic: - {2} -", - expected.Id, actual.Id, FormatDiagnostics(analyzer, actual))) - End If - - If (actual.Severity <> expected.Severity) Then - - Assert.IsTrue(False, -String.Format( -"Expected diagnostic severity to be ""{0}"" was ""{1}"" - -Diagnostic: - {2} -", - expected.Severity, actual.Severity, FormatDiagnostics(analyzer, actual))) - End If - - If (actual.GetMessage() <> expected.Message) Then - - Assert.IsTrue(False, -String.Format( -"Expected diagnostic message to be ""{0}"" was ""{1}"" - -Diagnostic: - {2} -", - expected.Message, actual.GetMessage(), FormatDiagnostics(analyzer, actual))) - End If - Next - End Sub - - ''' - ''' Helper method to VerifyDiagnosticResult that checks the location of a diagnostic And compares it with the location in the expected DiagnosticResult. - ''' - ''' The analyzer that was being run on the sources - ''' The diagnostic that was found in the code - ''' The Location of the Diagnostic found in the code - ''' The DiagnosticResultLocation that should have been found - Private Shared Sub VerifyDiagnosticLocation(analyzer As DiagnosticAnalyzer, diagnostic As Diagnostic, actual As Location, expected As DiagnosticResultLocation) - - Dim actualSpan = actual.GetLineSpan() - - Assert.IsTrue(actualSpan.Path = expected.Path OrElse (actualSpan.Path IsNot Nothing AndAlso actualSpan.Path.Contains("Test0.") AndAlso expected.Path.Contains("Test.")), - String.Format( -"Expected diagnostic to be in file ""{0}"" was actually in file ""{1}"" - -Diagnostic: - {2} -", - expected.Path, actualSpan.Path, FormatDiagnostics(analyzer, diagnostic))) - - Dim actualLinePosition = actualSpan.StartLinePosition - - ' Only check line position if there Is an actual line in the real diagnostic - If (actualLinePosition.Line > 0) Then - - If (actualLinePosition.Line + 1.0 <> expected.Line) Then - - Assert.IsTrue(False, - String.Format( -"Expected diagnostic to be on line ""{0}"" was actually on line ""{1}"" - -Diagnostic: - {2} -", - expected.Line, actualLinePosition.Line + 1, FormatDiagnostics(analyzer, diagnostic))) - End If - End If - - ' Only check column position if there Is an actual column position in the real diagnostic - If (actualLinePosition.Character > 0) Then - - If (actualLinePosition.Character + 1.0 <> expected.Column) Then - - Assert.IsTrue(False, - String.Format( -"Expected diagnostic to start at column ""{0}"" was actually at column ""{1}"" - -Diagnostic: - {2} -", - expected.Column, actualLinePosition.Character + 1, FormatDiagnostics(analyzer, diagnostic))) - End If - End If - End Sub -#End Region - -#Region " Formatting Diagnostics " - ''' - ''' Helper method to format a Diagnostic into an easily readable string - ''' - ''' The analyzer that this verifier tests - ''' The Diagnostics to be formatted - ''' The Diagnostics formatted as a string - Private Shared Function FormatDiagnostics(analyzer As DiagnosticAnalyzer, ParamArray diagnostics As Diagnostic()) As String - - Dim builder = New StringBuilder() - For i = 0 To diagnostics.Length - 1 - - builder.AppendLine("' " & diagnostics(i).ToString()) - - Dim analyzerType = analyzer.GetType() - Dim rules = analyzer.SupportedDiagnostics - - For Each rule In rules - - If (rule IsNot Nothing AndAlso rule.Id = diagnostics(i).Id) Then - - Dim location = diagnostics(i).Location - If (location = location.None) Then - - builder.AppendFormat("GetGlobalResult({0}.{1})", analyzerType.Name, rule.Id) - Else - - Assert.IsTrue(location.IsInSource, - $"Test base does not currently handle diagnostics in metadata locations. Diagnostic in metadata: {diagnostics(i)} - ") - - Dim resultMethodName As String = If(diagnostics(i).Location.SourceTree.FilePath.EndsWith(".cs"), "GetCSharpResultAt", "GetBasicResultAt") - Dim linePosition = diagnostics(i).Location.GetLineSpan().StartLinePosition - - builder.AppendFormat("{0}({1}, {2}, {3}.{4})", - resultMethodName, - linePosition.Line + 1, - linePosition.Character + 1, - analyzerType.Name, - rule.Id) - End If - - If i <> diagnostics.Length - 1 Then - - builder.Append(","c) - End If - - builder.AppendLine() - Exit For - End If - Next - Next - Return builder.ToString() - End Function -#End Region - End Class -End Namespace diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Resources.Designer.vb b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Resources.Designer.vb deleted file mode 100644 index c6b288ec9..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Resources.Designer.vb +++ /dev/null @@ -1,62 +0,0 @@ -'------------------------------------------------------------------------------ -' -' This code was generated by a tool. -' Runtime Version:4.0.30319.35317 -' -' Changes to this file may cause incorrect behavior and will be lost if -' the code is regenerated. -' -'------------------------------------------------------------------------------ - -Option Strict On -Option Explicit On - - -Namespace My.Resources - - 'This class was auto-generated by the StronglyTypedResourceBuilder - 'class via a tool like ResGen or Visual Studio. - 'To add or remove a member, edit your .ResX file then rerun ResGen - 'with the /str option, or rebuild your VS project. - ''' - ''' A strongly-typed resource class, for looking up localized strings, etc. - ''' - - Friend Module Resources - - Private resourceMan As Global.System.Resources.ResourceManager - - Private resourceCulture As Global.System.Globalization.CultureInfo - - ''' - ''' Returns the cached ResourceManager instance used by this class. - ''' - - Friend ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager - Get - If Object.ReferenceEquals(resourceMan, Nothing) Then - Dim temp As Global.System.Resources.ResourceManager = New Global.System.Resources.ResourceManager("$safeprojectname$.Resources", GetType(Resources).Assembly) - resourceMan = temp - End If - Return resourceMan - End Get - End Property - - ''' - ''' Overrides the current thread's CurrentUICulture property for all - ''' resource lookups using this strongly typed resource class. - ''' - - Friend Property Culture() As Global.System.Globalization.CultureInfo - Get - Return resourceCulture - End Get - Set(ByVal value As Global.System.Globalization.CultureInfo) - resourceCulture = value - End Set - End Property - End Module -End Namespace diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Resources.resx b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Resources.resx deleted file mode 100644 index af7dbebba..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Resources.resx +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Settings.Designer.vb b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Settings.Designer.vb deleted file mode 100644 index dada0b51f..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Settings.Designer.vb +++ /dev/null @@ -1,73 +0,0 @@ -'------------------------------------------------------------------------------ -' -' This code was generated by a tool. -' Runtime Version:4.0.30319.35317 -' -' Changes to this file may cause incorrect behavior and will be lost if -' the code is regenerated. -' -'------------------------------------------------------------------------------ - -Option Strict On -Option Explicit On - - -Namespace My - - - Partial Friend NotInheritable Class MySettings - Inherits Global.System.Configuration.ApplicationSettingsBase - - Private Shared defaultInstance As MySettings = CType(Global.System.Configuration.ApplicationSettingsBase.Synchronized(New MySettings), MySettings) - -#Region "My.Settings Auto-Save Functionality" -#If _MYTYPE = "WindowsForms" Then - Private Shared addedHandler As Boolean - - Private Shared addedHandlerLockObject As New Object - - _ - Private Shared Sub AutoSaveSettings(ByVal sender As Global.System.Object, ByVal e As Global.System.EventArgs) - If My.Application.SaveMySettingsOnExit Then - My.Settings.Save() - End If - End Sub -#End If -#End Region - - Public Shared ReadOnly Property [Default]() As MySettings - Get - -#If _MYTYPE = "WindowsForms" Then - If Not addedHandler Then - SyncLock addedHandlerLockObject - If Not addedHandler Then - AddHandler My.Application.Shutdown, AddressOf AutoSaveSettings - addedHandler = True - End If - End SyncLock - End If -#End If - Return defaultInstance - End Get - End Property - End Class -End Namespace - -Namespace My - - - Friend Module MySettingsProperty - - - Friend ReadOnly Property Settings() As Global.$safeprojectname$.My.MySettings - Get - Return Global.$safeprojectname$.My.MySettings.Default - End Get - End Property - End Module -End Namespace diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Settings.settings b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Settings.settings deleted file mode 100644 index 85b890b3c..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Test.vstemplate b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Test.vstemplate deleted file mode 100644 index b7855ba71..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/Test.vstemplate +++ /dev/null @@ -1,50 +0,0 @@ - - - UnitTestProject - <No description available> - VisualBasic - - - 1000 - true - UnitTestProject - true - Enabled - true - - - - - CodeFixVerifier.Helper.vb - DiagnosticResult.vb - DiagnosticVerifier.Helper.vb - Application.myapp - Application.Designer.vb - AssemblyInfo.vb - Resources.resx - Resources.Designer.vb - Settings.settings - Settings.Designer.vb - UnitTests.vb - CodeFixVerifier.vb - DiagnosticVerifier.vb - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKTestTemplateWizard - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/UnitTestProject.vbproj b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/UnitTestProject.vbproj deleted file mode 100644 index a986addcd..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/UnitTestProject.vbproj +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - Debug - AnyCPU - AnyCPU - {5399E7A8-F8F1-4F2E-A5D2-9C96F3DD2A2D} - Library - $safeprojectname$ - $safeprojectname$ - 512 - Windows - v$targetframeworkversion$ - - - true - full - true - true - bin\Debug\ - $safeprojectname$.xml - $(NoWarn);42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 - - - pdbonly - false - true - true - bin\Release\ - $safeprojectname$.xml - $(NoWarn);42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 - - - On - - - Binary - - - Off - - - On - - - false - - - - - - - - - - - - - - - - - - - - - - - True - Application.myapp - - - True - True - Resources.resx - - - True - Settings.settings - True - - - - - - - VbMyResourcesResXFileCodeGenerator - Resources.Designer.vb - My.Resources - Designer - - - - - MyApplicationCodeGenerator - Application.Designer.vb - - - SettingsSingleFileGenerator - My - Settings.Designer.vb - - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/UnitTests.vb b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/UnitTests.vb deleted file mode 100644 index 3adaf44be..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/UnitTests.vb +++ /dev/null @@ -1,63 +0,0 @@ -Imports $saferootprojectname$ -Imports $saferootprojectname$.Test.TestHelper -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.CodeFixes -Imports Microsoft.CodeAnalysis.Diagnostics -Imports Microsoft.VisualStudio.TestTools.UnitTesting - -Namespace $safeprojectname$ - - Public Class UnitTest - Inherits CodeFixVerifier - - 'No diagnostics expected to show up - - Public Sub TestMethod1() - Dim test = "" - VerifyBasicDiagnostic(test) - End Sub - - 'Diagnostic And CodeFix both triggered And checked for - - Public Sub TestMethod2() - - Dim test = " -Module Module1 - - Sub Main() - - End Sub - -End Module" - Dim expected = New DiagnosticResult With {.Id = "$saferootidentifiername$", - .Message = String.Format("Type name '{0}' contains lowercase letters", "Module1"), - .Severity = DiagnosticSeverity.Warning, - .Locations = New DiagnosticResultLocation() { - New DiagnosticResultLocation("Test0.vb", 2, 8) - } - } - - - VerifyBasicDiagnostic(test, expected) - - Dim fixtest = " -Module MODULE1 - - Sub Main() - - End Sub - -End Module" - VerifyBasicFix(test, fixtest) - End Sub - - Protected Overrides Function GetBasicCodeFixProvider() As CodeFixProvider - Return New $saferootidentifiername$CodeFixProvider() - End Function - - Protected Overrides Function GetBasicDiagnosticAnalyzer() As DiagnosticAnalyzer - Return New $saferootidentifiername$Analyzer() - End Function - - End Class -End Namespace diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Vsix/AssemblyInfo.vb b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Vsix/AssemblyInfo.vb deleted file mode 100644 index a0eddd25a..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Vsix/AssemblyInfo.vb +++ /dev/null @@ -1,32 +0,0 @@ -Imports System -Imports System.Reflection -Imports System.Runtime.InteropServices - -' General Information about an assembly is controlled through the following -' set of attributes. Change these attribute values to modify the information -' associated with an assembly. - -' Review the values of the assembly attributes - - - - - - - - - - -' Version information for an assembly consists of the following four values: -' -' Major Version -' Minor Version -' Build Number -' Revision -' -' You can specify all the values or you can default the Build and Revision Numbers -' by using the '*' as shown below: -' - - - diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Vsix/Deployment.vbproj b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Vsix/Deployment.vbproj deleted file mode 100644 index e7d2864c2..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Vsix/Deployment.vbproj +++ /dev/null @@ -1,79 +0,0 @@ - - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - Debug - AnyCPU - AnyCPU - 2.0 - {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} - $guid1$ - Library - $saferootprojectname$.Vsix - $saferootprojectname$.Vsix - v$targetframeworkversion$ - false - false - false - false - false - false - Roslyn - - - true - full - false - bin\Debug\ - true - true - prompt - $(NoWarn);41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 - - - pdbonly - true - bin\Release\ - false - true - prompt - $(NoWarn);41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 - - - On - - - Binary - - - Off - - - On - - - Program - $(DevEnvDir)devenv.exe - /rootsuffix Roslyn - - - - Designer - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Vsix/Deployment.vstemplate b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Vsix/Deployment.vstemplate deleted file mode 100644 index f7a035be6..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Vsix/Deployment.vstemplate +++ /dev/null @@ -1,25 +0,0 @@ - - - - Vsix - <No description available> - - VisualBasic - - 1000 - bb967cab-2ca5-4dac-8809-65b2b28a6350 - true - Vsix - true - - - - source.extension.vsixmanifest - AssemblyInfo.vb - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKVsixTemplateWizardThirdProject - - diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Vsix/source.extension.vsixmanifest b/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Vsix/source.extension.vsixmanifest deleted file mode 100644 index 7017b6470..000000000 --- a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Vsix/source.extension.vsixmanifest +++ /dev/null @@ -1,22 +0,0 @@ - - - - - $saferootprojectname$ - This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn"). - - - - - - - - - - - - - - - - diff --git a/src/Templates/VS2015/Templates.VisualStudio.2015.csproj b/src/Templates/VS2015/Templates.VisualStudio.2015.csproj deleted file mode 100644 index 9f9b8fbcf..000000000 --- a/src/Templates/VS2015/Templates.VisualStudio.2015.csproj +++ /dev/null @@ -1,213 +0,0 @@ - - - - net461 - Roslyn.SDK.VS2015 - Roslyn.SDK.VS2015 - false - false - false - false - false - - - - - - - - - - - - - - - - - - - Designer - Extensibility - - - Designer - Extensibility - - - Designer - Extensibility - - - - - - Designer - Extensibility - - - Designer - Extensibility - - - Designer - Extensibility - - - - - - Designer - Extensibility - - - Designer - Extensibility - - - Designer - Extensibility - - - - - - Designer - Extensibility - - - Designer - Extensibility - - - Designer - Extensibility - - - - - - SyntaxTree.bmp - true - - - SyntaxTree.ico - true - - - ThirdPartyNotices.rtf - true - - - EULA.rtf - true - - - - - - RoslynSDKTemplateWizard - - - - - SyntaxVisualizerExtension - BuiltProjectOutputGroup%3bGetCopyToOutputDirectoryItems%3bPkgdefProjectOutputGroup - DebugSymbolsProjectOutputGroup%3b - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %(IntermediateZipItem.Filename)\%(IntermediateZipItem.Language)\%(IntermediateZipItem.OutputSubPath)\%(IntermediateZipItem.Culture) - - - %(IntermediateZipProject.Filename)\%(IntermediateZipProject.Language)\%(IntermediateZipProject.OutputSubPath)\%(IntermediateZipProject.Culture) - - - Packages - - - Packages - - - Packages - - - Packages - - - Packages - - - Packages - - - Packages - - - Packages - - - Packages - - - Packages - - - Packages - - - - - - - - <_OriginalVSTemplate Include="@(VSTemplate)" /> - - - - - - - - - - $(GetVsixSourceItemsDependsOn);GetVsixTemplateItems - - \ No newline at end of file diff --git a/src/Templates/VS2015/VBDiagnostic.vstemplate b/src/Templates/VS2015/VBDiagnostic.vstemplate deleted file mode 100644 index f8c535444..000000000 --- a/src/Templates/VS2015/VBDiagnostic.vstemplate +++ /dev/null @@ -1,34 +0,0 @@ - - - - Analyzer with Code Fix (Portable Class Library) - Create a Visual Basic analyzer that comes with a code fix and generates diagnostics. The analyzer can be deployed as either a NuGet package or a VSIX extension. - VisualBasic - CodeInformation.ico - - 1000 - Analyzer - true - - - - - - ProjectTemplates\VisualBasic\Diagnostic\Analyzer\DiagnosticAnalyzer.vstemplate - - - ProjectTemplates\VisualBasic\Diagnostic\Test\Test.vstemplate - - - ProjectTemplates\VisualBasic\Diagnostic\Vsix\Deployment.vstemplate - - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKRootTemplateWizard - - \ No newline at end of file diff --git a/src/Templates/VS2015/VBRef.vstemplate b/src/Templates/VS2015/VBRef.vstemplate deleted file mode 100644 index 3f3451dad..000000000 --- a/src/Templates/VS2015/VBRef.vstemplate +++ /dev/null @@ -1,35 +0,0 @@ - - - - Code Refactoring (Portable Class Library) - Create a Visual Basic refactoring, deployed as a VSIX extension - VisualBasic - - 1000 - true - CodeRefactoring - true - Enabled - true - CodeInformation.ico - true - - - - - - ProjectTemplates\VisualBasic\CodeRefactoring\Ref\VBCodeRefactoring.vstemplate - - - ProjectTemplates\VisualBasic\CodeRefactoring\Vsix\Deployment.vstemplate - - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKRootTemplateWizard - - \ No newline at end of file diff --git a/src/Templates/VS2015/source.extension.vsixmanifest b/src/Templates/VS2015/source.extension.vsixmanifest deleted file mode 100644 index 4c073ecdc..000000000 --- a/src/Templates/VS2015/source.extension.vsixmanifest +++ /dev/null @@ -1,37 +0,0 @@ - - - - - .NET Compiler Platform SDK - The .NET Compiler Platform ("Roslyn") provides open-source C# and Visual Basic compilers with rich code analysis APIs. You can build code analysis tools with the same APIs that Microsoft is using to implement Visual Studio! Also includes the Syntax Visualizer, a Visual Studio extension that allows you to inspect and explore the syntax trees you'll use as you build applications and VS extensions atop the .NET Compiler Platform ("Roslyn"). - EULA.rtf - roslyn,template,SDK - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Templates/VS2017/CSRef.vstemplate b/src/Templates/VS2017/CSRef.vstemplate deleted file mode 100644 index bc980edb6..000000000 --- a/src/Templates/VS2017/CSRef.vstemplate +++ /dev/null @@ -1,36 +0,0 @@ - - - - Code Refactoring (.NET Standard) - Create a C# refactoring, deployed as a VSIX extension - CodeInformation.ico - CSharp - 2.0 - 1000 - Microsoft.CSharp.CodeRefactoring - true - true - CodeRefactoring - true - true - - - - - - ProjectTemplates\CSharp\CodeRefactoring\Ref\CSharpCodeRefactoring.vstemplate - - - ProjectTemplates\CSharp\CodeRefactoring\Vsix\Deployment.vstemplate - - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKRootTemplateWizard - - \ No newline at end of file diff --git a/src/Templates/VS2017/CSharpDiagnostic.vstemplate b/src/Templates/VS2017/CSharpDiagnostic.vstemplate deleted file mode 100644 index 8a18a38ab..000000000 --- a/src/Templates/VS2017/CSharpDiagnostic.vstemplate +++ /dev/null @@ -1,39 +0,0 @@ - - - - Analyzer with Code Fix (.NET Standard) - Create a C# analyzer that comes with a code fix and generates diagnostics. The analyzer can be deployed as either a NuGet package or a VSIX extension. - CodeInformation.ico - CSharp - 2.0 - 1000 - Microsoft.CSharp.Analyzer - true - true - Analyzer - true - true - - - - - - ProjectTemplates\CSharp\Diagnostic\Analyzer\DiagnosticAnalyzer.vstemplate - - - ProjectTemplates\CSharp\Diagnostic\Test\Test.vstemplate - - - ProjectTemplates\CSharp\Diagnostic\Vsix\Deployment.vstemplate - - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKRootTemplateWizard - - \ No newline at end of file diff --git a/src/Templates/VS2017/CodeInformation.ico b/src/Templates/VS2017/CodeInformation.ico deleted file mode 100644 index 4f75d3ee9..000000000 Binary files a/src/Templates/VS2017/CodeInformation.ico and /dev/null differ diff --git a/src/Templates/VS2017/Directory.Build.props b/src/Templates/VS2017/Directory.Build.props deleted file mode 100644 index 2a281d3d1..000000000 --- a/src/Templates/VS2017/Directory.Build.props +++ /dev/null @@ -1,13 +0,0 @@ - - - - Templates\$(MSBuildProjectName) - - - - false - - - - - \ No newline at end of file diff --git a/src/Templates/VS2017/ItemTemplates/CSharp/Analyzer/Analyzer.cs b/src/Templates/VS2017/ItemTemplates/CSharp/Analyzer/Analyzer.cs deleted file mode 100644 index fd52005aa..000000000 --- a/src/Templates/VS2017/ItemTemplates/CSharp/Analyzer/Analyzer.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace $rootnamespace$ -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class $safeitemname$ : DiagnosticAnalyzer - { - public const string DiagnosticId = "$safeitemname$"; - internal static readonly LocalizableString Title = "$safeitemname$ Title"; - internal static readonly LocalizableString MessageFormat = "$safeitemname$ '{0}'"; - internal const string Category = "$safeitemname$ Category"; - - internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, true); - - public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } } - - public override void Initialize(AnalysisContext context) - { - } - } -} diff --git a/src/Templates/VS2017/ItemTemplates/CSharp/Analyzer/CSharpAnalyzer.vstemplate b/src/Templates/VS2017/ItemTemplates/CSharp/Analyzer/CSharpAnalyzer.vstemplate deleted file mode 100644 index c762c2cf0..000000000 --- a/src/Templates/VS2017/ItemTemplates/CSharp/Analyzer/CSharpAnalyzer.vstemplate +++ /dev/null @@ -1,14 +0,0 @@ - - - Microsoft.CSharp.Class - Analyzer.cs - Analyzer - Create a C# diagnostic analyzer. - CSharp - 10 - - - - Analyzer.cs - - \ No newline at end of file diff --git a/src/Templates/VS2017/ItemTemplates/CSharp/CodeFix/CSharpCodeFix.vstemplate b/src/Templates/VS2017/ItemTemplates/CSharp/CodeFix/CSharpCodeFix.vstemplate deleted file mode 100644 index a9cee2198..000000000 --- a/src/Templates/VS2017/ItemTemplates/CSharp/CodeFix/CSharpCodeFix.vstemplate +++ /dev/null @@ -1,14 +0,0 @@ - - - Microsoft.CSharp.Class - CodeFix.cs - CodeFix - Create a C# code fix. - CSharp - 10 - - - - CodeFix.cs - - \ No newline at end of file diff --git a/src/Templates/VS2017/ItemTemplates/CSharp/CodeFix/CodeFix.cs b/src/Templates/VS2017/ItemTemplates/CSharp/CodeFix/CodeFix.cs deleted file mode 100644 index 489b3743e..000000000 --- a/src/Templates/VS2017/ItemTemplates/CSharp/CodeFix/CodeFix.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Composition; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Rename; -using Microsoft.CodeAnalysis.Text; - -namespace $rootnamespace$ -{ - [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof($safeitemname$)), Shared] - public class $safeitemname$ : CodeFixProvider - { - // TODO: Replace with actual diagnostic id that should trigger this fix. - public const string DiagnosticId = "$safeitemname$"; - - public sealed override ImmutableArray FixableDiagnosticIds - { - get { return ImmutableArray.Create(DiagnosticId); } - } - - public sealed override FixAllProvider GetFixAllProvider() - { - return WellKnownFixAllProviders.BatchFixer; - } - - public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Templates/VS2017/ItemTemplates/CSharp/Refactoring/CSharpRefactoring.vstemplate b/src/Templates/VS2017/ItemTemplates/CSharp/Refactoring/CSharpRefactoring.vstemplate deleted file mode 100644 index e5a6b6392..000000000 --- a/src/Templates/VS2017/ItemTemplates/CSharp/Refactoring/CSharpRefactoring.vstemplate +++ /dev/null @@ -1,14 +0,0 @@ - - - Microsoft.CSharp.Class - Refactoring.cs - Refactoring - Create a C# code refactoring. - CSharp - 10 - - - - Refactoring.cs - - \ No newline at end of file diff --git a/src/Templates/VS2017/ItemTemplates/CSharp/Refactoring/Refactoring.cs b/src/Templates/VS2017/ItemTemplates/CSharp/Refactoring/Refactoring.cs deleted file mode 100644 index 82f751124..000000000 --- a/src/Templates/VS2017/ItemTemplates/CSharp/Refactoring/Refactoring.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Composition; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Rename; -using Microsoft.CodeAnalysis.Text; - -namespace $rootnamespace$ -{ - [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof($safeitemname$)), Shared] - internal class $safeitemname$ : CodeRefactoringProvider - { - public sealed override Task ComputeRefactoringsAsync(CodeRefactoringContext context) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Templates/VS2017/ItemTemplates/VisualBasic/Analyzer/Analyzer.vb b/src/Templates/VS2017/ItemTemplates/VisualBasic/Analyzer/Analyzer.vb deleted file mode 100644 index 34725d49c..000000000 --- a/src/Templates/VS2017/ItemTemplates/VisualBasic/Analyzer/Analyzer.vb +++ /dev/null @@ -1,28 +0,0 @@ -Imports System.Collections.Immutable -Imports System.Threading -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Diagnostics -Imports Microsoft.CodeAnalysis.Text -Imports Microsoft.CodeAnalysis.VisualBasic -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax - - -Public Class $safeitemname$ - Inherits DiagnosticAnalyzer - - Public Const DiagnosticId = "$safeitemname$" - Friend Shared ReadOnly Title As LocalizableString = "$safeitemname$ Title" - Friend Shared ReadOnly MessageFormat As LocalizableString = "$safeitemname$ '{0}'" - Friend Const Category = "$safeitemname$ Category" - - Friend Shared Rule As New DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, True) - - Public Overrides ReadOnly Property SupportedDiagnostics As ImmutableArray(Of DiagnosticDescriptor) - Get - Return ImmutableArray.Create(Rule) - End Get - End Property - - Public Overrides Sub Initialize(context As AnalysisContext) - End Sub -End Class diff --git a/src/Templates/VS2017/ItemTemplates/VisualBasic/Analyzer/VBAnalyzer.vstemplate b/src/Templates/VS2017/ItemTemplates/VisualBasic/Analyzer/VBAnalyzer.vstemplate deleted file mode 100644 index da5ef883d..000000000 --- a/src/Templates/VS2017/ItemTemplates/VisualBasic/Analyzer/VBAnalyzer.vstemplate +++ /dev/null @@ -1,15 +0,0 @@ - - - - Microsoft.VisualBasic.Internal.AssemblyInfo - Analyzer.vb - Analyzer - Create a Visual Basic diagnostic analyzer. - VisualBasic - 10 - - - - Analyzer.vb - - \ No newline at end of file diff --git a/src/Templates/VS2017/ItemTemplates/VisualBasic/CodeFix/CodeFix.vb b/src/Templates/VS2017/ItemTemplates/VisualBasic/CodeFix/CodeFix.vb deleted file mode 100644 index 69d440f0b..000000000 --- a/src/Templates/VS2017/ItemTemplates/VisualBasic/CodeFix/CodeFix.vb +++ /dev/null @@ -1,33 +0,0 @@ -Imports System.Composition -Imports System.Collections.Immutable -Imports System.Threading -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.CodeActions -Imports Microsoft.CodeAnalysis.CodeFixes -Imports Microsoft.CodeAnalysis.CodeRefactorings -Imports Microsoft.CodeAnalysis.Rename -Imports Microsoft.CodeAnalysis.Text -Imports Microsoft.CodeAnalysis.VisualBasic -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax - - -Public Class $safeitemname$ - Inherits CodeFixProvider - - ' TODO: Replace with actual diagnostic id that should trigger this fix. - Public Const DiagnosticId As String = "$safeitemname$" - - Public NotOverridable Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) - Get - Return ImmutableArray.Create(DiagnosticId) - End Get - End Property - - Public NotOverridable Overrides Function GetFixAllProvider() As FixAllProvider - Return WellKnownFixAllProviders.BatchFixer - End Function - - Public NotOverridable Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Throw New NotImplementedException() - End Function -End Class diff --git a/src/Templates/VS2017/ItemTemplates/VisualBasic/CodeFix/VBCodeFix.vstemplate b/src/Templates/VS2017/ItemTemplates/VisualBasic/CodeFix/VBCodeFix.vstemplate deleted file mode 100644 index b0fba26ac..000000000 --- a/src/Templates/VS2017/ItemTemplates/VisualBasic/CodeFix/VBCodeFix.vstemplate +++ /dev/null @@ -1,15 +0,0 @@ - - - - Microsoft.VisualBasic.Internal.AssemblyInfo - CodeFix.vb - CodeFix - Create a Visual Basic code fix. - VisualBasic - 10 - - - - CodeFix.vb - - \ No newline at end of file diff --git a/src/Templates/VS2017/ItemTemplates/VisualBasic/Refactoring/Refactoring.vb b/src/Templates/VS2017/ItemTemplates/VisualBasic/Refactoring/Refactoring.vb deleted file mode 100644 index 416dd6e7a..000000000 --- a/src/Templates/VS2017/ItemTemplates/VisualBasic/Refactoring/Refactoring.vb +++ /dev/null @@ -1,18 +0,0 @@ -Imports System.Composition -Imports System.Threading -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.CodeActions -Imports Microsoft.CodeAnalysis.CodeRefactorings -Imports Microsoft.CodeAnalysis.Rename -Imports Microsoft.CodeAnalysis.Text -Imports Microsoft.CodeAnalysis.VisualBasic -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax - - -Friend Class $safeitemname$ - Inherits CodeRefactoringProvider - - Public NotOverridable Overrides Function ComputeRefactoringsAsync(context As CodeRefactoringContext) As Task - Throw New NotImplementedException() - End Function -End Class diff --git a/src/Templates/VS2017/ItemTemplates/VisualBasic/Refactoring/VBRefactoring.vstemplate b/src/Templates/VS2017/ItemTemplates/VisualBasic/Refactoring/VBRefactoring.vstemplate deleted file mode 100644 index 14af755cb..000000000 --- a/src/Templates/VS2017/ItemTemplates/VisualBasic/Refactoring/VBRefactoring.vstemplate +++ /dev/null @@ -1,15 +0,0 @@ - - - - Microsoft.VisualBasic.Internal.AssemblyInfo - Refactoring.vb - Refactoring - Create a Visual Basic refactoring. - VisualBasic - 10 - - - - Refactoring.vb - - \ No newline at end of file diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/CodeRefactoring/Ref/CSharpCodeRefactoring.vstemplate b/src/Templates/VS2017/ProjectTemplates/CSharp/CodeRefactoring/Ref/CSharpCodeRefactoring.vstemplate deleted file mode 100644 index 558295fcc..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/CodeRefactoring/Ref/CSharpCodeRefactoring.vstemplate +++ /dev/null @@ -1,28 +0,0 @@ - - - - Code Refactoring (VSIX) - Create a C# refactoring, deployed as a VSIX extension - - CSharp - 2.0 - 951 - Microsoft.CSharp.CodeRefactoring.ClassLib - true - true - CodeRefactoring - true - true - - - - CodeRefactoringProvider.cs - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKAnalyzerTemplateWizard - - \ No newline at end of file diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/CodeRefactoring/Ref/CodeRefactoring.csproj b/src/Templates/VS2017/ProjectTemplates/CSharp/CodeRefactoring/Ref/CodeRefactoring.csproj deleted file mode 100644 index b1573c5d4..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/CodeRefactoring/Ref/CodeRefactoring.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - netstandard1.3 - - - - - - - - diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/CodeRefactoring/Ref/CodeRefactoringProvider.cs b/src/Templates/VS2017/ProjectTemplates/CSharp/CodeRefactoring/Ref/CodeRefactoringProvider.cs deleted file mode 100644 index d80381d0e..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/CodeRefactoring/Ref/CodeRefactoringProvider.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Collections.Generic; -using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Rename; -using Microsoft.CodeAnalysis.Text; - -namespace $safeprojectname$ -{ - [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof($saferootidentifiername$CodeRefactoringProvider)), Shared] - internal class $saferootidentifiername$CodeRefactoringProvider : CodeRefactoringProvider - { - public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) - { - // TODO: Replace the following code with your own analysis, generating a CodeAction for each refactoring to offer - - var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - - // Find the node at the selection. - var node = root.FindNode(context.Span); - - // Only offer a refactoring if the selected node is a type declaration node. - var typeDecl = node as TypeDeclarationSyntax; - if (typeDecl == null) - { - return; - } - - // For any type declaration node, create a code action to reverse the identifier text. - var action = CodeAction.Create("Reverse type name", c => ReverseTypeNameAsync(context.Document, typeDecl, c)); - - // Register this code action. - context.RegisterRefactoring(action); - } - - private async Task ReverseTypeNameAsync(Document document, TypeDeclarationSyntax typeDecl, CancellationToken cancellationToken) - { - // Produce a reversed version of the type declaration's identifier token. - var identifierToken = typeDecl.Identifier; - var newName = new string(identifierToken.Text.ToCharArray().Reverse().ToArray()); - - // Get the symbol representing the type to be renamed. - var semanticModel = await document.GetSemanticModelAsync(cancellationToken); - var typeSymbol = semanticModel.GetDeclaredSymbol(typeDecl, cancellationToken); - - // Produce a new solution that has all references to that type renamed, including the declaration. - var originalSolution = document.Project.Solution; - var optionSet = originalSolution.Workspace.Options; - var newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(false); - - // Return the new solution with the now-uppercase type name. - return newSolution; - } - } -} diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/CodeRefactoring/Vsix/Deployment.csproj b/src/Templates/VS2017/ProjectTemplates/CSharp/CodeRefactoring/Vsix/Deployment.csproj deleted file mode 100644 index 5c2d63c8f..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/CodeRefactoring/Vsix/Deployment.csproj +++ /dev/null @@ -1,58 +0,0 @@ - - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - Debug - AnyCPU - AnyCPU - 2.0 - {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - $guid1$ - Library - Properties - $saferootprojectname$.Vsix - $saferootprojectname$ - v4.6.1 - false - false - false - false - false - false - Roslyn - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - Program - $(DevEnvDir)devenv.exe - /rootsuffix Roslyn - - - - Designer - - - - - \ No newline at end of file diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/CodeRefactoring/Vsix/Deployment.vstemplate b/src/Templates/VS2017/ProjectTemplates/CSharp/CodeRefactoring/Vsix/Deployment.vstemplate deleted file mode 100644 index d5e7b1d17..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/CodeRefactoring/Vsix/Deployment.vstemplate +++ /dev/null @@ -1,28 +0,0 @@ - - - - Vsix - <No description available> - - CSharp - 2.0 - 1000 - bb967cab-2ca5-4dac-8809-65b2b28a6350 - true - true - Vsix - true - true - - - - source.extension.vsixmanifest - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKVsixTemplateWizardSecondProject - - \ No newline at end of file diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/CodeRefactoring/Vsix/source.extension.vsixmanifest b/src/Templates/VS2017/ProjectTemplates/CSharp/CodeRefactoring/Vsix/source.extension.vsixmanifest deleted file mode 100644 index 30ec37ee5..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/CodeRefactoring/Vsix/source.extension.vsixmanifest +++ /dev/null @@ -1,21 +0,0 @@ - - - - - $saferootprojectname$ - This is a sample code refactoring extension for the .NET Compiler Platform ("Roslyn"). - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/ConsoleApplication/CSharpConsoleApplication.vstemplate b/src/Templates/VS2017/ProjectTemplates/CSharp/ConsoleApplication/CSharpConsoleApplication.vstemplate deleted file mode 100644 index 6923dc1d5..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/ConsoleApplication/CSharpConsoleApplication.vstemplate +++ /dev/null @@ -1,24 +0,0 @@ - - - - Stand-Alone Code Analysis Tool - Create a code analysis command-line application - CodeAnalysisConsole.ico - CSharp - 2.0 - 950 - Microsoft.CSharp.StandaloneCodeAnalysis - true - true - CodeAnalysisApp - true - true - - - - Program.cs - - - \ No newline at end of file diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/ConsoleApplication/CodeAnalysisConsole.ico b/src/Templates/VS2017/ProjectTemplates/CSharp/ConsoleApplication/CodeAnalysisConsole.ico deleted file mode 100644 index 1b0f918e2..000000000 Binary files a/src/Templates/VS2017/ProjectTemplates/CSharp/ConsoleApplication/CodeAnalysisConsole.ico and /dev/null differ diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/ConsoleApplication/ConsoleApplication.csproj b/src/Templates/VS2017/ProjectTemplates/CSharp/ConsoleApplication/ConsoleApplication.csproj deleted file mode 100644 index 444a079d3..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/ConsoleApplication/ConsoleApplication.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - Exe - net461 - - - - - - - - - diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/ConsoleApplication/Program.cs b/src/Templates/VS2017/ProjectTemplates/CSharp/ConsoleApplication/Program.cs deleted file mode 100644 index a2dd01570..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/ConsoleApplication/Program.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.MSBuild; -using Microsoft.CodeAnalysis.Text; - -namespace $safeprojectname$ -{ - class Program - { - static void Main(string[] args) - { - var workspace = MSBuildWorkspace.Create(); - } - } -} diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/CodeFixProvider.cs b/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/CodeFixProvider.cs deleted file mode 100644 index 6380fb592..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/CodeFixProvider.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Rename; -using Microsoft.CodeAnalysis.Text; - -namespace $saferootprojectname$ -{ - [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof($saferootidentifiername$CodeFixProvider)), Shared] - public class $saferootidentifiername$CodeFixProvider : CodeFixProvider - { - private const string title = "Make uppercase"; - - public sealed override ImmutableArray FixableDiagnosticIds - { - get { return ImmutableArray.Create($saferootidentifiername$Analyzer.DiagnosticId); } - } - - public sealed override FixAllProvider GetFixAllProvider() - { - // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers - return WellKnownFixAllProviders.BatchFixer; - } - - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - - // TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest - var diagnostic = context.Diagnostics.First(); - var diagnosticSpan = diagnostic.Location.SourceSpan; - - // Find the type declaration identified by the diagnostic. - var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); - - // Register a code action that will invoke the fix. - context.RegisterCodeFix( - CodeAction.Create( - title: title, - createChangedSolution: c => MakeUppercaseAsync(context.Document, declaration, c), - equivalenceKey: title), - diagnostic); - } - - private async Task MakeUppercaseAsync(Document document, TypeDeclarationSyntax typeDecl, CancellationToken cancellationToken) - { - // Compute new uppercase name. - var identifierToken = typeDecl.Identifier; - var newName = identifierToken.Text.ToUpperInvariant(); - - // Get the symbol representing the type to be renamed. - var semanticModel = await document.GetSemanticModelAsync(cancellationToken); - var typeSymbol = semanticModel.GetDeclaredSymbol(typeDecl, cancellationToken); - - // Produce a new solution that has all references to that type renamed, including the declaration. - var originalSolution = document.Project.Solution; - var optionSet = originalSolution.Workspace.Options; - var newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(false); - - // Return the new solution with the now-uppercase type name. - return newSolution; - } - } -} diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.cs b/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.cs deleted file mode 100644 index 9b3329cdf..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace $saferootprojectname$ -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class $saferootidentifiername$Analyzer : DiagnosticAnalyzer - { - public const string DiagnosticId = "$saferootidentifiername$"; - - // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat. - // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Localizing%20Analyzers.md for more on localization - private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources)); - private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources)); - private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources)); - private const string Category = "Naming"; - - private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); - - public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } } - - public override void Initialize(AnalysisContext context) - { - // TODO: Consider registering other actions that act on syntax instead of or in addition to symbols - // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Analyzer%20Actions%20Semantics.md for more information - context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType); - } - - private static void AnalyzeSymbol(SymbolAnalysisContext context) - { - // TODO: Replace the following code with your own analysis, generating Diagnostic objects for any issues you find - var namedTypeSymbol = (INamedTypeSymbol)context.Symbol; - - // Find just those named type symbols with names containing lowercase letters. - if (namedTypeSymbol.Name.ToCharArray().Any(char.IsLower)) - { - // For all such symbols, produce a diagnostic. - var diagnostic = Diagnostic.Create(Rule, namedTypeSymbol.Locations[0], namedTypeSymbol.Name); - - context.ReportDiagnostic(diagnostic); - } - } - } -} diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.csproj b/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.csproj deleted file mode 100644 index d0e486e26..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - netstandard1.3 - false - True - - - - $safeprojectname$ - 1.0.0.0 - $username$ - http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE - http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE - http://ICON_URL_HERE_OR_DELETE_THIS_LINE - http://REPOSITORY_URL_HERE_OR_DELETE_THIS_LINE - false - $safeprojectname$ - Summary of changes made in this release of the package. - Copyright - $safeprojectname$, analyzers - true - - - - - - - - - - - - - - - - - - diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.vstemplate b/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.vstemplate deleted file mode 100644 index 4766bbadb..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.vstemplate +++ /dev/null @@ -1,33 +0,0 @@ - - - - DiagnosticAnalyzer - <No description available> - - CSharp - 2.0 - 952 - bb967cab-2ca5-4dac-8809-65b2b28a6350 - true - true - DiagnosticAnalyzer - true - true - - - - DiagnosticAnalyzer.cs - CodeFixProvider.cs - Resources.resx - Resources.Designer.cs - install.ps1 - uninstall.ps1 - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKAnalyzerTemplateWizard - - diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/Resources.Designer.cs b/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/Resources.Designer.cs deleted file mode 100644 index 8f98a010a..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/Resources.Designer.cs +++ /dev/null @@ -1,106 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.0 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace $saferootprojectname$ -{ - using System; - using System.Reflection; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if (object.ReferenceEquals(resourceMan, null)) - { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("$saferootprojectname$.Resources", typeof(Resources).GetTypeInfo().Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { - return resourceCulture; - } - set - { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Type names should be all uppercase.. - /// - internal static string AnalyzerDescription - { - get - { - return ResourceManager.GetString("AnalyzerDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type name '{0}' contains lowercase letters. - /// - internal static string AnalyzerMessageFormat - { - get - { - return ResourceManager.GetString("AnalyzerMessageFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type name contains lowercase letters. - /// - internal static string AnalyzerTitle - { - get - { - return ResourceManager.GetString("AnalyzerTitle", resourceCulture); - } - } - } -} diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/Resources.resx b/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/Resources.resx deleted file mode 100644 index 410edccd7..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Analyzer/Resources.resx +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 - - - Type names should be all uppercase. - An optional longer localizable description of the diagnostic. - - - Type name '{0}' contains lowercase letters - The format-able message the diagnostic displays. - - - Type name contains lowercase letters - The title of the diagnostic. - - \ No newline at end of file diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/CodeFixVerifier.Helper.cs b/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/CodeFixVerifier.Helper.cs deleted file mode 100644 index 6d3204816..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/CodeFixVerifier.Helper.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Simplification; -using System.Collections.Generic; -using System.Linq; -using System.Threading; - -namespace TestHelper -{ - /// - /// Diagnostic Producer class with extra methods dealing with applying codefixes - /// All methods are static - /// - public abstract partial class CodeFixVerifier : DiagnosticVerifier - { - /// - /// Apply the inputted CodeAction to the inputted document. - /// Meant to be used to apply codefixes. - /// - /// The Document to apply the fix on - /// A CodeAction that will be applied to the Document. - /// A Document with the changes from the CodeAction - private static Document ApplyFix(Document document, CodeAction codeAction) - { - var operations = codeAction.GetOperationsAsync(CancellationToken.None).Result; - var solution = operations.OfType().Single().ChangedSolution; - return solution.GetDocument(document.Id); - } - - /// - /// Compare two collections of Diagnostics,and return a list of any new diagnostics that appear only in the second collection. - /// Note: Considers Diagnostics to be the same if they have the same Ids. In the case of multiple diagnostics with the same Id in a row, - /// this method may not necessarily return the new one. - /// - /// The Diagnostics that existed in the code before the CodeFix was applied - /// The Diagnostics that exist in the code after the CodeFix was applied - /// A list of Diagnostics that only surfaced in the code after the CodeFix was applied - private static IEnumerable GetNewDiagnostics(IEnumerable diagnostics, IEnumerable newDiagnostics) - { - var oldArray = diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); - var newArray = newDiagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); - - int oldIndex = 0; - int newIndex = 0; - - while (newIndex < newArray.Length) - { - if (oldIndex < oldArray.Length && oldArray[oldIndex].Id == newArray[newIndex].Id) - { - ++oldIndex; - ++newIndex; - } - else - { - yield return newArray[newIndex++]; - } - } - } - - /// - /// Get the existing compiler diagnostics on the inputted document. - /// - /// The Document to run the compiler diagnostic analyzers on - /// The compiler diagnostics that were found in the code - private static IEnumerable GetCompilerDiagnostics(Document document) - { - return document.GetSemanticModelAsync().Result.GetDiagnostics(); - } - - /// - /// Given a document, turn it into a string based on the syntax root - /// - /// The Document to be converted to a string - /// A string containing the syntax of the Document after formatting - private static string GetStringFromDocument(Document document) - { - var simplifiedDoc = Simplifier.ReduceAsync(document, Simplifier.Annotation).Result; - var root = simplifiedDoc.GetSyntaxRootAsync().Result; - root = Formatter.Format(root, Formatter.Annotation, simplifiedDoc.Project.Solution.Workspace); - return root.GetText().ToString(); - } - } -} - diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/CodeFixVerifier.cs b/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/CodeFixVerifier.cs deleted file mode 100644 index 641daa93c..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/CodeFixVerifier.cs +++ /dev/null @@ -1,128 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Collections.Generic; -using System.Linq; -using System.Threading; - -namespace TestHelper -{ - /// - /// Superclass of all Unit tests made for diagnostics with codefixes. - /// Contains methods used to verify correctness of codefixes - /// - public abstract partial class CodeFixVerifier : DiagnosticVerifier - { - /// - /// Returns the codefix being tested (C#) - to be implemented in non-abstract class - /// - /// The CodeFixProvider to be used for CSharp code - protected virtual CodeFixProvider GetCSharpCodeFixProvider() - { - return null; - } - - /// - /// Returns the codefix being tested (VB) - to be implemented in non-abstract class - /// - /// The CodeFixProvider to be used for VisualBasic code - protected virtual CodeFixProvider GetBasicCodeFixProvider() - { - return null; - } - - /// - /// Called to test a C# codefix when applied on the inputted string as a source - /// - /// A class in the form of a string before the CodeFix was applied to it - /// A class in the form of a string after the CodeFix was applied to it - /// Index determining which codefix to apply if there are multiple - /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied - protected void VerifyCSharpFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false) - { - VerifyFix(LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), GetCSharpCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics); - } - - /// - /// Called to test a VB codefix when applied on the inputted string as a source - /// - /// A class in the form of a string before the CodeFix was applied to it - /// A class in the form of a string after the CodeFix was applied to it - /// Index determining which codefix to apply if there are multiple - /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied - protected void VerifyBasicFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false) - { - VerifyFix(LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), GetBasicCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics); - } - - /// - /// General verifier for codefixes. - /// Creates a Document from the source string, then gets diagnostics on it and applies the relevant codefixes. - /// Then gets the string after the codefix is applied and compares it with the expected result. - /// Note: If any codefix causes new diagnostics to show up, the test fails unless allowNewCompilerDiagnostics is set to true. - /// - /// The language the source code is in - /// The analyzer to be applied to the source code - /// The codefix to be applied to the code wherever the relevant Diagnostic is found - /// A class in the form of a string before the CodeFix was applied to it - /// A class in the form of a string after the CodeFix was applied to it - /// Index determining which codefix to apply if there are multiple - /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied - private void VerifyFix(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string oldSource, string newSource, int? codeFixIndex, bool allowNewCompilerDiagnostics) - { - var document = CreateDocument(oldSource, language); - var analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); - var compilerDiagnostics = GetCompilerDiagnostics(document); - var attempts = analyzerDiagnostics.Length; - - for (int i = 0; i < attempts; ++i) - { - var actions = new List(); - var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a), CancellationToken.None); - codeFixProvider.RegisterCodeFixesAsync(context).Wait(); - - if (!actions.Any()) - { - break; - } - - if (codeFixIndex != null) - { - document = ApplyFix(document, actions.ElementAt((int)codeFixIndex)); - break; - } - - document = ApplyFix(document, actions.ElementAt(0)); - analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); - - var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)); - - //check if applying the code fix introduced any new compiler diagnostics - if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any()) - { - // Format and get the compiler diagnostics again so that the locations make sense in the output - document = document.WithSyntaxRoot(Formatter.Format(document.GetSyntaxRootAsync().Result, Formatter.Annotation, document.Project.Solution.Workspace)); - newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)); - - Assert.IsTrue(false, - string.Format("Fix introduced new compiler diagnostics:\r\n{0}\r\n\r\nNew document:\r\n{1}\r\n", - string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString())), - document.GetSyntaxRootAsync().Result.ToFullString())); - } - - //check if there are analyzer diagnostics left after the code fix - if (!analyzerDiagnostics.Any()) - { - break; - } - } - - //after applying all of the code fixes, compare the resulting string to the inputted one - var actual = GetStringFromDocument(document); - Assert.AreEqual(newSource, actual); - } - } -} diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/DiagnosticResult.cs b/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/DiagnosticResult.cs deleted file mode 100644 index dde80c43f..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/DiagnosticResult.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Microsoft.CodeAnalysis; -using System; - -namespace TestHelper -{ - /// - /// Location where the diagnostic appears, as determined by path, line number, and column number. - /// - public struct DiagnosticResultLocation - { - public DiagnosticResultLocation(string path, int line, int column) - { - if (line < -1) - { - throw new ArgumentOutOfRangeException(nameof(line), "line must be >= -1"); - } - - if (column < -1) - { - throw new ArgumentOutOfRangeException(nameof(column), "column must be >= -1"); - } - - this.Path = path; - this.Line = line; - this.Column = column; - } - - public string Path { get; } - public int Line { get; } - public int Column { get; } - } - - /// - /// Struct that stores information about a Diagnostic appearing in a source - /// - public struct DiagnosticResult - { - private DiagnosticResultLocation[] locations; - - public DiagnosticResultLocation[] Locations - { - get - { - if (this.locations == null) - { - this.locations = new DiagnosticResultLocation[] { }; - } - return this.locations; - } - - set - { - this.locations = value; - } - } - - public DiagnosticSeverity Severity { get; set; } - - public string Id { get; set; } - - public string Message { get; set; } - - public string Path - { - get - { - return this.Locations.Length > 0 ? this.Locations[0].Path : ""; - } - } - - public int Line - { - get - { - return this.Locations.Length > 0 ? this.Locations[0].Line : -1; - } - } - - public int Column - { - get - { - return this.Locations.Length > 0 ? this.Locations[0].Column : -1; - } - } - } -} diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/DiagnosticVerifier.Helper.cs b/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/DiagnosticVerifier.Helper.cs deleted file mode 100644 index de72e54f8..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/DiagnosticVerifier.Helper.cs +++ /dev/null @@ -1,170 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Text; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; - -namespace TestHelper -{ - /// - /// Class for turning strings into documents and getting the diagnostics on them - /// All methods are static - /// - public abstract partial class DiagnosticVerifier - { - private static readonly MetadataReference CorlibReference = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); - private static readonly MetadataReference SystemCoreReference = MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location); - private static readonly MetadataReference CSharpSymbolsReference = MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location); - private static readonly MetadataReference CodeAnalysisReference = MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location); - - internal static string DefaultFilePathPrefix = "Test"; - internal static string CSharpDefaultFileExt = "cs"; - internal static string VisualBasicDefaultExt = "vb"; - internal static string TestProjectName = "TestProject"; - - #region Get Diagnostics - - /// - /// Given classes in the form of strings, their language, and an IDiagnosticAnalyzer to apply to it, return the diagnostics found in the string after converting it to a document. - /// - /// Classes in the form of strings - /// The language the source classes are in - /// The analyzer to be run on the sources - /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location - private static Diagnostic[] GetSortedDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer) - { - return GetSortedDiagnosticsFromDocuments(analyzer, GetDocuments(sources, language)); - } - - /// - /// Given an analyzer and a document to apply it to, run the analyzer and gather an array of diagnostics found in it. - /// The returned diagnostics are then ordered by location in the source document. - /// - /// The analyzer to run on the documents - /// The Documents that the analyzer will be run on - /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location - protected static Diagnostic[] GetSortedDiagnosticsFromDocuments(DiagnosticAnalyzer analyzer, Document[] documents) - { - var projects = new HashSet(); - foreach (var document in documents) - { - projects.Add(document.Project); - } - - var diagnostics = new List(); - foreach (var project in projects) - { - var compilationWithAnalyzers = project.GetCompilationAsync().Result.WithAnalyzers(ImmutableArray.Create(analyzer)); - var diags = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().Result; - foreach (var diag in diags) - { - if (diag.Location == Location.None || diag.Location.IsInMetadata) - { - diagnostics.Add(diag); - } - else - { - for (int i = 0; i < documents.Length; i++) - { - var document = documents[i]; - var tree = document.GetSyntaxTreeAsync().Result; - if (tree == diag.Location.SourceTree) - { - diagnostics.Add(diag); - } - } - } - } - } - - var results = SortDiagnostics(diagnostics); - diagnostics.Clear(); - return results; - } - - /// - /// Sort diagnostics by location in source document - /// - /// The list of Diagnostics to be sorted - /// An IEnumerable containing the Diagnostics in order of Location - private static Diagnostic[] SortDiagnostics(IEnumerable diagnostics) - { - return diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); - } - - #endregion - - #region Set up compilation and documents - /// - /// Given an array of strings as sources and a language, turn them into a project and return the documents and spans of it. - /// - /// Classes in the form of strings - /// The language the source code is in - /// A Tuple containing the Documents produced from the sources and their TextSpans if relevant - private static Document[] GetDocuments(string[] sources, string language) - { - if (language != LanguageNames.CSharp && language != LanguageNames.VisualBasic) - { - throw new ArgumentException("Unsupported Language"); - } - - var project = CreateProject(sources, language); - var documents = project.Documents.ToArray(); - - if (sources.Length != documents.Length) - { - throw new InvalidOperationException("Amount of sources did not match amount of Documents created"); - } - - return documents; - } - - /// - /// Create a Document from a string through creating a project that contains it. - /// - /// Classes in the form of a string - /// The language the source code is in - /// A Document created from the source string - protected static Document CreateDocument(string source, string language = LanguageNames.CSharp) - { - return CreateProject(new[] { source }, language).Documents.First(); - } - - /// - /// Create a project using the inputted strings as sources. - /// - /// Classes in the form of strings - /// The language the source code is in - /// A Project created out of the Documents created from the source strings - private static Project CreateProject(string[] sources, string language = LanguageNames.CSharp) - { - string fileNamePrefix = DefaultFilePathPrefix; - string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt; - - var projectId = ProjectId.CreateNewId(debugName: TestProjectName); - - var solution = new AdhocWorkspace() - .CurrentSolution - .AddProject(projectId, TestProjectName, TestProjectName, language) - .AddMetadataReference(projectId, CorlibReference) - .AddMetadataReference(projectId, SystemCoreReference) - .AddMetadataReference(projectId, CSharpSymbolsReference) - .AddMetadataReference(projectId, CodeAnalysisReference); - - int count = 0; - foreach (var source in sources) - { - var newFileName = fileNamePrefix + count + "." + fileExt; - var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); - solution = solution.AddDocument(documentId, newFileName, SourceText.From(source)); - count++; - } - return solution.GetProject(projectId); - } - #endregion - } -} - diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/DiagnosticVerifier.cs b/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/DiagnosticVerifier.cs deleted file mode 100644 index 8ae0aa9c6..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/DiagnosticVerifier.cs +++ /dev/null @@ -1,270 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TestHelper -{ - /// - /// Superclass of all Unit Tests for DiagnosticAnalyzers - /// - public abstract partial class DiagnosticVerifier - { - #region To be implemented by Test classes - /// - /// Get the CSharp analyzer being tested - to be implemented in non-abstract class - /// - protected virtual DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() - { - return null; - } - - /// - /// Get the Visual Basic analyzer being tested (C#) - to be implemented in non-abstract class - /// - protected virtual DiagnosticAnalyzer GetBasicDiagnosticAnalyzer() - { - return null; - } - #endregion - - #region Verifier wrappers - - /// - /// Called to test a C# DiagnosticAnalyzer when applied on the single inputted string as a source - /// Note: input a DiagnosticResult for each Diagnostic expected - /// - /// A class in the form of a string to run the analyzer on - /// DiagnosticResults that should appear after the analyzer is run on the source - protected void VerifyCSharpDiagnostic(string source, params DiagnosticResult[] expected) - { - VerifyDiagnostics(new[] { source }, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); - } - - /// - /// Called to test a VB DiagnosticAnalyzer when applied on the single inputted string as a source - /// Note: input a DiagnosticResult for each Diagnostic expected - /// - /// A class in the form of a string to run the analyzer on - /// DiagnosticResults that should appear after the analyzer is run on the source - protected void VerifyBasicDiagnostic(string source, params DiagnosticResult[] expected) - { - VerifyDiagnostics(new[] { source }, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected); - } - - /// - /// Called to test a C# DiagnosticAnalyzer when applied on the inputted strings as a source - /// Note: input a DiagnosticResult for each Diagnostic expected - /// - /// An array of strings to create source documents from to run the analyzers on - /// DiagnosticResults that should appear after the analyzer is run on the sources - protected void VerifyCSharpDiagnostic(string[] sources, params DiagnosticResult[] expected) - { - VerifyDiagnostics(sources, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); - } - - /// - /// Called to test a VB DiagnosticAnalyzer when applied on the inputted strings as a source - /// Note: input a DiagnosticResult for each Diagnostic expected - /// - /// An array of strings to create source documents from to run the analyzers on - /// DiagnosticResults that should appear after the analyzer is run on the sources - protected void VerifyBasicDiagnostic(string[] sources, params DiagnosticResult[] expected) - { - VerifyDiagnostics(sources, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected); - } - - /// - /// General method that gets a collection of actual diagnostics found in the source after the analyzer is run, - /// then verifies each of them. - /// - /// An array of strings to create source documents from to run the analyzers on - /// The language of the classes represented by the source strings - /// The analyzer to be run on the source code - /// DiagnosticResults that should appear after the analyzer is run on the sources - private void VerifyDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expected) - { - var diagnostics = GetSortedDiagnostics(sources, language, analyzer); - VerifyDiagnosticResults(diagnostics, analyzer, expected); - } - - #endregion - - #region Actual comparisons and verifications - /// - /// Checks each of the actual Diagnostics found and compares them with the corresponding DiagnosticResult in the array of expected results. - /// Diagnostics are considered equal only if the DiagnosticResultLocation, Id, Severity, and Message of the DiagnosticResult match the actual diagnostic. - /// - /// The Diagnostics found by the compiler after running the analyzer on the source code - /// The analyzer that was being run on the sources - /// Diagnostic Results that should have appeared in the code - private static void VerifyDiagnosticResults(IEnumerable actualResults, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expectedResults) - { - int expectedCount = expectedResults.Count(); - int actualCount = actualResults.Count(); - - if (expectedCount != actualCount) - { - string diagnosticsOutput = actualResults.Any() ? FormatDiagnostics(analyzer, actualResults.ToArray()) : " NONE."; - - Assert.IsTrue(false, - string.Format("Mismatch between number of diagnostics returned, expected \"{0}\" actual \"{1}\"\r\n\r\nDiagnostics:\r\n{2}\r\n", expectedCount, actualCount, diagnosticsOutput)); - } - - for (int i = 0; i < expectedResults.Length; i++) - { - var actual = actualResults.ElementAt(i); - var expected = expectedResults[i]; - - if (expected.Line == -1 && expected.Column == -1) - { - if (actual.Location != Location.None) - { - Assert.IsTrue(false, - string.Format("Expected:\nA project diagnostic with No location\nActual:\n{0}", - FormatDiagnostics(analyzer, actual))); - } - } - else - { - VerifyDiagnosticLocation(analyzer, actual, actual.Location, expected.Locations.First()); - var additionalLocations = actual.AdditionalLocations.ToArray(); - - if (additionalLocations.Length != expected.Locations.Length - 1) - { - Assert.IsTrue(false, - string.Format("Expected {0} additional locations but got {1} for Diagnostic:\r\n {2}\r\n", - expected.Locations.Length - 1, additionalLocations.Length, - FormatDiagnostics(analyzer, actual))); - } - - for (int j = 0; j < additionalLocations.Length; ++j) - { - VerifyDiagnosticLocation(analyzer, actual, additionalLocations[j], expected.Locations[j + 1]); - } - } - - if (actual.Id != expected.Id) - { - Assert.IsTrue(false, - string.Format("Expected diagnostic id to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Id, actual.Id, FormatDiagnostics(analyzer, actual))); - } - - if (actual.Severity != expected.Severity) - { - Assert.IsTrue(false, - string.Format("Expected diagnostic severity to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Severity, actual.Severity, FormatDiagnostics(analyzer, actual))); - } - - if (actual.GetMessage() != expected.Message) - { - Assert.IsTrue(false, - string.Format("Expected diagnostic message to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Message, actual.GetMessage(), FormatDiagnostics(analyzer, actual))); - } - } - } - - /// - /// Helper method to VerifyDiagnosticResult that checks the location of a diagnostic and compares it with the location in the expected DiagnosticResult. - /// - /// The analyzer that was being run on the sources - /// The diagnostic that was found in the code - /// The Location of the Diagnostic found in the code - /// The DiagnosticResultLocation that should have been found - private static void VerifyDiagnosticLocation(DiagnosticAnalyzer analyzer, Diagnostic diagnostic, Location actual, DiagnosticResultLocation expected) - { - var actualSpan = actual.GetLineSpan(); - - Assert.IsTrue(actualSpan.Path == expected.Path || (actualSpan.Path != null && actualSpan.Path.Contains("Test0.") && expected.Path.Contains("Test.")), - string.Format("Expected diagnostic to be in file \"{0}\" was actually in file \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Path, actualSpan.Path, FormatDiagnostics(analyzer, diagnostic))); - - var actualLinePosition = actualSpan.StartLinePosition; - - // Only check line position if there is an actual line in the real diagnostic - if (actualLinePosition.Line > 0) - { - if (actualLinePosition.Line + 1 != expected.Line) - { - Assert.IsTrue(false, - string.Format("Expected diagnostic to be on line \"{0}\" was actually on line \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Line, actualLinePosition.Line + 1, FormatDiagnostics(analyzer, diagnostic))); - } - } - - // Only check column position if there is an actual column position in the real diagnostic - if (actualLinePosition.Character > 0) - { - if (actualLinePosition.Character + 1 != expected.Column) - { - Assert.IsTrue(false, - string.Format("Expected diagnostic to start at column \"{0}\" was actually at column \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Column, actualLinePosition.Character + 1, FormatDiagnostics(analyzer, diagnostic))); - } - } - } - #endregion - - #region Formatting Diagnostics - /// - /// Helper method to format a Diagnostic into an easily readable string - /// - /// The analyzer that this verifier tests - /// The Diagnostics to be formatted - /// The Diagnostics formatted as a string - private static string FormatDiagnostics(DiagnosticAnalyzer analyzer, params Diagnostic[] diagnostics) - { - var builder = new StringBuilder(); - for (int i = 0; i < diagnostics.Length; ++i) - { - builder.AppendLine("// " + diagnostics[i].ToString()); - - var analyzerType = analyzer.GetType(); - var rules = analyzer.SupportedDiagnostics; - - foreach (var rule in rules) - { - if (rule != null && rule.Id == diagnostics[i].Id) - { - var location = diagnostics[i].Location; - if (location == Location.None) - { - builder.AppendFormat("GetGlobalResult({0}.{1})", analyzerType.Name, rule.Id); - } - else - { - Assert.IsTrue(location.IsInSource, - $"Test base does not currently handle diagnostics in metadata locations. Diagnostic in metadata: {diagnostics[i]}\r\n"); - - string resultMethodName = diagnostics[i].Location.SourceTree.FilePath.EndsWith(".cs") ? "GetCSharpResultAt" : "GetBasicResultAt"; - var linePosition = diagnostics[i].Location.GetLineSpan().StartLinePosition; - - builder.AppendFormat("{0}({1}, {2}, {3}.{4})", - resultMethodName, - linePosition.Line + 1, - linePosition.Character + 1, - analyzerType.Name, - rule.Id); - } - - if (i != diagnostics.Length - 1) - { - builder.Append(','); - } - - builder.AppendLine(); - break; - } - } - } - return builder.ToString(); - } - #endregion - } -} diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/Test.csproj b/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/Test.csproj deleted file mode 100644 index f2835fd5f..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/Test.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - netcoreapp2.0 - - - - - - - - - - - - - - diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/Test.vstemplate b/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/Test.vstemplate deleted file mode 100644 index c649d7c8b..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/Test.vstemplate +++ /dev/null @@ -1,33 +0,0 @@ - - - - TestProject - <No description available> - - CSharp - 2.0 - 1000 - bb967cab-2ca5-4dac-8809-65b2b28a6351 - true - true - TestProject - true - true - - - - UnitTests.cs - CodeFixVerifier.Helper.cs - DiagnosticResult.cs - DiagnosticVerifier.Helper.cs - CodeFixVerifier.cs - DiagnosticVerifier.cs - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKTestTemplateWizard - - diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/UnitTests.cs b/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/UnitTests.cs deleted file mode 100644 index 2fb1db9f2..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Test/UnitTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using TestHelper; -using $saferootprojectname$; - -namespace $safeprojectname$ -{ - [TestClass] - public class UnitTest : CodeFixVerifier - { - - //No diagnostics expected to show up - [TestMethod] - public void TestMethod1() - { - var test = @""; - - VerifyCSharpDiagnostic(test); - } - - //Diagnostic and CodeFix both triggered and checked for - [TestMethod] - public void TestMethod2() - { - var test = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - namespace ConsoleApplication1 - { - class TypeName - { - } - }"; - var expected = new DiagnosticResult - { - Id = "$saferootidentifiername$", - Message = String.Format("Type name '{0}' contains lowercase letters", "TypeName"), - Severity = DiagnosticSeverity.Warning, - Locations = - new[] { - new DiagnosticResultLocation("Test0.cs", 11, 15) - } - }; - - VerifyCSharpDiagnostic(test, expected); - - var fixtest = @" - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using System.Diagnostics; - - namespace ConsoleApplication1 - { - class TYPENAME - { - } - }"; - VerifyCSharpFix(test, fixtest); - } - - protected override CodeFixProvider GetCSharpCodeFixProvider() - { - return new $saferootidentifiername$CodeFixProvider(); - } - - protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() - { - return new $saferootidentifiername$Analyzer(); - } - } -} diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Vsix/Deployment.csproj b/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Vsix/Deployment.csproj deleted file mode 100644 index 5c2d63c8f..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Vsix/Deployment.csproj +++ /dev/null @@ -1,58 +0,0 @@ - - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - Debug - AnyCPU - AnyCPU - 2.0 - {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - $guid1$ - Library - Properties - $saferootprojectname$.Vsix - $saferootprojectname$ - v4.6.1 - false - false - false - false - false - false - Roslyn - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - Program - $(DevEnvDir)devenv.exe - /rootsuffix Roslyn - - - - Designer - - - - - \ No newline at end of file diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Vsix/Deployment.vstemplate b/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Vsix/Deployment.vstemplate deleted file mode 100644 index a0da1d6bc..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Vsix/Deployment.vstemplate +++ /dev/null @@ -1,28 +0,0 @@ - - - - Vsix - <No description available> - - CSharp - 2.0 - 1000 - bb967cab-2ca5-4dac-8809-65b2b28a6350 - true - true - Vsix - true - true - - - - source.extension.vsixmanifest - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKVsixTemplateWizardThirdProject - - \ No newline at end of file diff --git a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Vsix/source.extension.vsixmanifest b/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Vsix/source.extension.vsixmanifest deleted file mode 100644 index f0db02098..000000000 --- a/src/Templates/VS2017/ProjectTemplates/CSharp/Diagnostic/Vsix/source.extension.vsixmanifest +++ /dev/null @@ -1,22 +0,0 @@ - - - - - $saferootprojectname$ - This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn"). - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/CodeRefactoring.vbproj b/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/CodeRefactoring.vbproj deleted file mode 100644 index 0963e21f6..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/CodeRefactoring.vbproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - netstandard1.3 - - - - - - - - diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/CodeRefactoringProvider.vb b/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/CodeRefactoringProvider.vb deleted file mode 100644 index 8f40efa04..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/CodeRefactoringProvider.vb +++ /dev/null @@ -1,56 +0,0 @@ -Imports System.Collections.Generic -Imports System.Composition -Imports System.Linq -Imports System.Threading -Imports System.Threading.Tasks -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.CodeActions -Imports Microsoft.CodeAnalysis.CodeRefactorings -Imports Microsoft.CodeAnalysis.VisualBasic -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports Microsoft.CodeAnalysis.Rename -Imports Microsoft.CodeAnalysis.Text - - -Friend Class $saferootidentifiername$CodeRefactoringProvider - Inherits CodeRefactoringProvider - - Public NotOverridable Overrides Async Function ComputeRefactoringsAsync(context As CodeRefactoringContext) As Task - ' TODO: Replace the following code with your own analysis, generating a CodeAction for each refactoring to offer - - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) - - ' Find the node at the selection. - Dim node = root.FindNode(context.Span) - - ' Only offer a refactoring if the selected node is a type statement node. - Dim typeDecl = TryCast(node, TypeStatementSyntax) - If typeDecl Is Nothing Then - Return - End If - - ' For any type statement node, create a code action to reverse the identifier text. - Dim action = CodeAction.Create("Reverse type name", Function(c) ReverseTypeNameAsync(context.Document, typeDecl, c)) - - ' Register this code action. - context.RegisterRefactoring(action) - End Function - - Private Async Function ReverseTypeNameAsync(document As Document, typeStmt As TypeStatementSyntax, cancellationToken As CancellationToken) As Task(Of Solution) - ' Produce a reversed version of the type statement's identifier token. - Dim identifierToken = typeStmt.Identifier - Dim newName = New String(identifierToken.Text.ToCharArray.Reverse.ToArray) - - ' Get the symbol representing the type to be renamed. - Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken) - Dim typeSymbol = semanticModel.GetDeclaredSymbol(typeStmt, cancellationToken) - - ' Produce a new solution that has all references to that type renamed, including the declaration. - Dim originalSolution = document.Project.Solution - Dim optionSet = originalSolution.Workspace.Options - Dim newSolution = Await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(False) - - ' Return the new solution with the now-uppercase type name. - Return newSolution - End Function -End Class diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/VBCodeRefactoring.vstemplate b/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/VBCodeRefactoring.vstemplate deleted file mode 100644 index 6e4fce350..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/VBCodeRefactoring.vstemplate +++ /dev/null @@ -1,28 +0,0 @@ - - - - Code Refactoring (VSIX) - Create a Visual Basic refactoring, deployed as a VSIX extension - - VisualBasic - 2.0 - 1000 - Microsoft.VisualBasic.CodeRefactoring.ClassLib - true - true - CodeRefactoring - true - true - - - - CodeRefactoringProvider.vb - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKAnalyzerTemplateWizard - - \ No newline at end of file diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/Deployment.vbproj b/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/Deployment.vbproj deleted file mode 100644 index a29000005..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/Deployment.vbproj +++ /dev/null @@ -1,79 +0,0 @@ - - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - Debug - AnyCPU - AnyCPU - 2.0 - {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} - $guid1$ - Library - $saferootprojectname$.Vsix - $saferootprojectname$ - v4.6.1 - false - false - false - false - false - false - Roslyn - - - true - full - false - bin\Debug\ - true - true - prompt - $(NoWarn);41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 - - - pdbonly - true - bin\Release\ - false - true - prompt - $(NoWarn);41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 - - - On - - - Binary - - - Off - - - On - - - Program - $(DevEnvDir)devenv.exe - /rootsuffix Roslyn - - - - Designer - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/Deployment.vstemplate b/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/Deployment.vstemplate deleted file mode 100644 index b5c554dbc..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/Deployment.vstemplate +++ /dev/null @@ -1,29 +0,0 @@ - - - - Vsix - <No description available> - - VisualBasic - 2.0 - 1000 - bb967cab-2ca5-4dac-8809-65b2b28a6350 - true - true - Vsix - true - true - - - - source.extension.vsixmanifest - AssemblyInfo.vb - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKVsixTemplateWizardSecondProject - - diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/source.extension.vsixmanifest b/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/source.extension.vsixmanifest deleted file mode 100644 index bedc4dd0d..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/source.extension.vsixmanifest +++ /dev/null @@ -1,21 +0,0 @@ - - - - - $saferootprojectname$ - This is a sample code refactoring extension for the .NET Compiler Platform ("Roslyn"). - - - - - - - - - - - - - - - diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/ConsoleApplication/CodeAnalysisConsole.ico b/src/Templates/VS2017/ProjectTemplates/VisualBasic/ConsoleApplication/CodeAnalysisConsole.ico deleted file mode 100644 index 1b0f918e2..000000000 Binary files a/src/Templates/VS2017/ProjectTemplates/VisualBasic/ConsoleApplication/CodeAnalysisConsole.ico and /dev/null differ diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/ConsoleApplication/ConsoleApplication.vbproj b/src/Templates/VS2017/ProjectTemplates/VisualBasic/ConsoleApplication/ConsoleApplication.vbproj deleted file mode 100644 index fc54402cb..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/ConsoleApplication/ConsoleApplication.vbproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - Exe - net461 - - - - - - - - - diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/ConsoleApplication/Module1.vb b/src/Templates/VS2017/ProjectTemplates/VisualBasic/ConsoleApplication/Module1.vb deleted file mode 100644 index a9d99df58..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/ConsoleApplication/Module1.vb +++ /dev/null @@ -1,18 +0,0 @@ -Imports System -Imports System.Collections.Generic -Imports System.Linq -Imports System.Text -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.VisualBasic -Imports Microsoft.CodeAnalysis.VisualBasic.Symbols -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports Microsoft.CodeAnalysis.MSBuild -Imports Microsoft.CodeAnalysis.Text - -Module $safeprojectname$ - - Sub Main() - Dim workspace = MSBuildWorkspace.Create - End Sub - -End Module diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/ConsoleApplication/VBConsoleApplication.vstemplate b/src/Templates/VS2017/ProjectTemplates/VisualBasic/ConsoleApplication/VBConsoleApplication.vstemplate deleted file mode 100644 index 862efc4f1..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/ConsoleApplication/VBConsoleApplication.vstemplate +++ /dev/null @@ -1,24 +0,0 @@ - - - - Stand-Alone Code Analysis Tool - Create a code analysis command-line application - CodeAnalysisConsole.ico - VisualBasic - 2.0 - 950 - Microsoft.VisualBasic.StandaloneCodeAnalysis - true - true - CodeAnalysisApp - true - true - - - - Module1.vb - - - diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/CodeFixProvider.vb b/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/CodeFixProvider.vb deleted file mode 100644 index d7809633c..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/CodeFixProvider.vb +++ /dev/null @@ -1,70 +0,0 @@ -Imports System -Imports System.Collections.Generic -Imports System.Collections.Immutable -Imports System.Composition -Imports System.Linq -Imports System.Threading -Imports System.Threading.Tasks -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.CodeFixes -Imports Microsoft.CodeAnalysis.CodeActions -Imports Microsoft.CodeAnalysis.VisualBasic -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports Microsoft.CodeAnalysis.Rename -Imports Microsoft.CodeAnalysis.Text - - -Public Class $saferootidentifiername$CodeFixProvider - Inherits CodeFixProvider - - Private Const title As String = "Make uppercase" - - Public NotOverridable Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) - Get - Return ImmutableArray.Create($saferootidentifiername$Analyzer.DiagnosticId) - End Get - End Property - - Public NotOverridable Overrides Function GetFixAllProvider() As FixAllProvider - ' See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers - Return WellKnownFixAllProviders.BatchFixer - End Function - - Public NotOverridable Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task - Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) - - ' TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest - - Dim diagnostic = context.Diagnostics.First() - Dim diagnosticSpan = diagnostic.Location.SourceSpan - - ' Find the type statement identified by the diagnostic. - Dim declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType(Of TypeStatementSyntax)().First() - - ' Register a code action that will invoke the fix. - context.RegisterCodeFix( - CodeAction.Create( - title:=title, - createChangedSolution:=Function(c) MakeUppercaseAsync(context.Document, declaration, c), - equivalenceKey:=title), - diagnostic) - End Function - - Private Async Function MakeUppercaseAsync(document As Document, typeStmt As TypeStatementSyntax, cancellationToken As CancellationToken) As Task(Of Solution) - ' Compute new uppercase name. - Dim identifierToken = typeStmt.Identifier - Dim newName = identifierToken.Text.ToUpperInvariant() - - ' Get the symbol representing the type to be renamed. - Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken) - Dim typeSymbol = semanticModel.GetDeclaredSymbol(typeStmt, cancellationToken) - - ' Produce a new solution that has all references to that type renamed, including the declaration. - Dim originalSolution = document.Project.Solution - Dim optionSet = originalSolution.Workspace.Options - Dim newSolution = Await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(False) - - ' Return the new solution with the now-uppercase type name. - Return newSolution - End Function -End Class diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Diagnostic.vbproj b/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Diagnostic.vbproj deleted file mode 100644 index 0e2981fe7..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Diagnostic.vbproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - netstandard1.3 - false - True - - - - $safeprojectname$ - 1.0.0.0 - $username$ - http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE - http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE - http://ICON_URL_HERE_OR_DELETE_THIS_LINE - http://REPOSITORY_URL_HERE_OR_DELETE_THIS_LINE - false - $safeprojectname$ - Summary of changes made in this release of the package. - Copyright - $safeprojectname$, analyzers - true - - - - - - - - - - - - - - - - - - diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/DiagnosticAnalyzer.vb b/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/DiagnosticAnalyzer.vb deleted file mode 100644 index 083fa2fe8..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/DiagnosticAnalyzer.vb +++ /dev/null @@ -1,51 +0,0 @@ -Imports System -Imports System.Collections.Generic -Imports System.Collections.Immutable -Imports System.Linq -Imports System.Threading -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.VisualBasic -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports Microsoft.CodeAnalysis.Diagnostics - - -Public Class $saferootidentifiername$Analyzer - Inherits DiagnosticAnalyzer - - Public Const DiagnosticId = "$saferootidentifiername$" - - ' You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat. - ' See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Localizing%20Analyzers.md for more on localization - Private Shared ReadOnly Title As LocalizableString = New LocalizableResourceString(NameOf(My.Resources.AnalyzerTitle), My.Resources.ResourceManager, GetType(My.Resources.Resources)) - Private Shared ReadOnly MessageFormat As LocalizableString = New LocalizableResourceString(NameOf(My.Resources.AnalyzerMessageFormat), My.Resources.ResourceManager, GetType(My.Resources.Resources)) - Private Shared ReadOnly Description As LocalizableString = New LocalizableResourceString(NameOf(My.Resources.AnalyzerDescription), My.Resources.ResourceManager, GetType(My.Resources.Resources)) - Private Const Category = "Naming" - - Private Shared Rule As New DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault:=True, description:=Description) - - Public Overrides ReadOnly Property SupportedDiagnostics As ImmutableArray(Of DiagnosticDescriptor) - Get - Return ImmutableArray.Create(Rule) - End Get - End Property - - Public Overrides Sub Initialize(context As AnalysisContext) - ' TODO: Consider registering other actions that act on syntax instead of or in addition to symbols - ' See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Analyzer%20Actions%20Semantics.md for more information - context.RegisterSymbolAction(AddressOf AnalyzeSymbol, SymbolKind.NamedType) - End Sub - - Private Sub AnalyzeSymbol(context As SymbolAnalysisContext) - ' TODO: Replace the following code with your own analysis, generating Diagnostic objects for any issues you find - - Dim namedTypeSymbol = CType(context.Symbol, INamedTypeSymbol) - - ' Find just those named type symbols with names containing lowercase letters. - If namedTypeSymbol.Name.ToCharArray.Any(AddressOf Char.IsLower) Then - ' For all such symbols, produce a diagnostic. - Dim diag = Diagnostic.Create(Rule, namedTypeSymbol.Locations(0), namedTypeSymbol.Name) - - context.ReportDiagnostic(diag) - End If - End Sub -End Class diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/DiagnosticAnalyzer.vstemplate b/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/DiagnosticAnalyzer.vstemplate deleted file mode 100644 index 773954573..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/DiagnosticAnalyzer.vstemplate +++ /dev/null @@ -1,33 +0,0 @@ - - - - DiagnosticAnalyzer - <No description available> - - VisualBasic - 2.0 - 1000 - bb967cab-2ca5-4dac-8809-65b2b28a6350 - true - true - DiagnosticAnalyzer - true - true - - - - DiagnosticAnalyzer.vb - CodeFixProvider.vb - Resources.resx - Resources.Designer.vb - install.ps1 - uninstall.ps1 - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKAnalyzerTemplateWizard - - \ No newline at end of file diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Resources.Designer.vb b/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Resources.Designer.vb deleted file mode 100644 index e6012411a..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Resources.Designer.vb +++ /dev/null @@ -1,91 +0,0 @@ -'------------------------------------------------------------------------------ -' -' This code was generated by a tool. -' Runtime Version:4.0.30319.0 -' -' Changes to this file may cause incorrect behavior and will be lost if -' the code is regenerated. -' -'------------------------------------------------------------------------------ - -Option Strict On -Option Explicit On - -Imports System -Imports System.Reflection - -Namespace My.Resources - - 'This class was auto-generated by the StronglyTypedResourceBuilder - 'class via a tool like ResGen or Visual Studio. - 'To add or remove a member, edit your .ResX file then rerun ResGen - 'with the /str option, or rebuild your VS project. - ''' - ''' A strongly-typed resource class, for looking up localized strings, etc. - ''' - - Friend Module Resources - - Private resourceMan As Global.System.Resources.ResourceManager - - Private resourceCulture As Global.System.Globalization.CultureInfo - - ''' - ''' Returns the cached ResourceManager instance used by this class. - ''' - - Friend ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager - Get - If Object.ReferenceEquals(resourceMan, Nothing) Then - Dim temp As Global.System.Resources.ResourceManager = New Global.System.Resources.ResourceManager("$saferootprojectname$.Resources", GetType(Resources).GetTypeInfo.Assembly) - resourceMan = temp - End If - Return resourceMan - End Get - End Property - - ''' - ''' Overrides the current thread's CurrentUICulture property for all - ''' resource lookups using this strongly typed resource class. - ''' - - Friend Property Culture() As Global.System.Globalization.CultureInfo - Get - Return resourceCulture - End Get - Set - resourceCulture = Value - End Set - End Property - - ''' - ''' Looks up a localized string similar to Type name '{0}' contains lowercase letters. - ''' - Friend ReadOnly Property AnalyzerMessageFormat() As String - Get - Return ResourceManager.GetString("AnalyzerMessageFormat", resourceCulture) - End Get - End Property - - ''' - ''' Looks up a localized string similar to Type name contains lowercase letters. - ''' - Friend ReadOnly Property AnalyzerTitle() As String - Get - Return ResourceManager.GetString("AnalyzerTitle", resourceCulture) - End Get - End Property - - ''' - ''' Looks up a localized string similar to Type names should be all uppercase. - ''' - Friend ReadOnly Property AnalyzerDescription() As String - Get - Return ResourceManager.GetString("AnalyzerDescription", resourceCulture) - End Get - End Property - End Module -End Namespace diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/CodeFixVerifier.Helper.vb b/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/CodeFixVerifier.Helper.vb deleted file mode 100644 index f74275f2e..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/CodeFixVerifier.Helper.vb +++ /dev/null @@ -1,77 +0,0 @@ -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.CodeActions -Imports Microsoft.CodeAnalysis.Formatting -Imports Microsoft.CodeAnalysis.Simplification -Imports System.Threading - -Namespace TestHelper - ' Diagnostic Producer class with extra methods dealing with applying codefixes - ' All methods are shared - Partial Public MustInherit Class CodeFixVerifier - Inherits DiagnosticVerifier - ''' - ''' Apply the inputted CodeAction to the inputted document. - ''' Meant to be used to apply codefixes. - ''' - ''' The Document to apply the fix on - ''' A CodeAction that will be applied to the Document. - ''' A Document with the changes from the CodeAction - Private Shared Function ApplyFix(document As Document, codeAction As CodeAction) As Document - Dim operations = codeAction.GetOperationsAsync(CancellationToken.None).Result - Dim solution = operations.OfType(Of ApplyChangesOperation).Single.ChangedSolution - Return solution.GetDocument(document.Id) - End Function - - - ''' - ''' Compare two collections of Diagnostics, and return a list of any New diagnostics that appear only in the second collection. - ''' Note: Considers Diagnostics to be the same if they have the same Ids. In the case of multiple diagnostics With the same Id In a row, - ''' this method may not necessarily return the new one. - ''' - ''' The Diagnostics that existed in the code before the CodeFix was applied - ''' The Diagnostics that exist in the code after the CodeFix was applied - ''' A list of Diagnostics that only surfaced in the code after the CodeFix was applied - Private Shared Iterator Function GetNewDiagnostics(diagnostics As IEnumerable(Of Diagnostic), newDiagnostics As IEnumerable(Of Diagnostic)) As IEnumerable(Of Diagnostic) - - Dim oldArray = diagnostics.OrderBy(Function(d) d.Location.SourceSpan.Start).ToArray() - Dim newArray = newDiagnostics.OrderBy(Function(d) d.Location.SourceSpan.Start).ToArray() - - Dim oldIndex = 0 - Dim newIndex = 0 - - While (newIndex < newArray.Length) - - If (oldIndex < oldArray.Length AndAlso oldArray(oldIndex).Id = newArray(newIndex).Id) Then - oldIndex += 1 - newIndex += 1 - Else - Yield newArray(newIndex) - newIndex += 1 - End If - End While - - End Function - - ''' - ''' Get the existing compiler diagnostics on the inputted document. - ''' - ''' The Document to run the compiler diagnostic analyzers on - ''' The compiler diagnostics that were found in the code - Private Shared Function GetCompilerDiagnostics(document As Document) As IEnumerable(Of Diagnostic) - Return document.GetSemanticModelAsync().Result.GetDiagnostics() - End Function - - ''' - ''' Given a Document, turn it into a string based on the syntax root - ''' - ''' The Document to be converted to a string - ''' A string containing the syntax of the Document after formatting - Private Shared Function GetStringFromDocument(document As Document) As String - Dim simplifiedDoc = Simplifier.ReduceAsync(document, Simplifier.Annotation).Result - Dim root = simplifiedDoc.GetSyntaxRootAsync().Result - root = Formatter.Format(root, Formatter.Annotation, simplifiedDoc.Project.Solution.Workspace) - Return root.GetText().ToString() - End Function - End Class -End Namespace - diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/CodeFixVerifier.vb b/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/CodeFixVerifier.vb deleted file mode 100644 index 3c782bcf5..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/CodeFixVerifier.vb +++ /dev/null @@ -1,117 +0,0 @@ -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.CodeActions -Imports Microsoft.CodeAnalysis.CodeFixes -Imports Microsoft.CodeAnalysis.Diagnostics -Imports Microsoft.CodeAnalysis.Formatting -Imports Microsoft.VisualStudio.TestTools.UnitTesting -Imports System.Collections.Generic -Imports System.Threading - -Namespace TestHelper - ''' - ''' Superclass of all Unit tests made for diagnostics with codefixes. - ''' Contains methods used to verify correctness of codefixes - ''' - Partial Public MustInherit Class CodeFixVerifier - Inherits DiagnosticVerifier - ''' - ''' Returns the codefix being tested (C#) - to be implemented in non-abstract class - ''' - ''' The CodeFixProvider to be used for CSharp code - Protected Overridable Function GetCSharpCodeFixProvider() As CodeFixProvider - Return Nothing - End Function - - ''' - ''' Returns the codefix being tested (VB) - to be implemented in non-abstract class - ''' - ''' The CodeFixProvider to be used for VisualBasic code - Protected Overridable Function GetBasicCodeFixProvider() As CodeFixProvider - Return Nothing - End Function - - ''' - ''' Called to test a C# codefix when applied on the inputted string as a source - ''' - ''' A class in the form of a string before the CodeFix was applied to it - ''' A class in the form of a string after the CodeFix was applied to it - ''' Index determining which codefix to apply if there are multiple - ''' A bool controlling whether Or Not the test will fail if the CodeFix introduces other warnings after being applied - Protected Sub VerifyCSharpFix(oldSource As String, newSource As String, Optional codeFixIndex As Integer? = Nothing, Optional allowNewCompilerDiagnostics As Boolean = False) - VerifyFix(LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), GetCSharpCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics) - End Sub - - ''' - ''' Called to test a VB codefix when applied on the inputted string as a source - ''' - ''' A class in the form of a string before the CodeFix was applied to it - ''' A class in the form of a string after the CodeFix was applied to it - ''' Index determining which codefix to apply if there are multiple - ''' A bool controlling whether Or Not the test will fail if the CodeFix introduces other warnings after being applied - Protected Sub VerifyBasicFix(oldSource As String, newSource As String, Optional codeFixIndex As Integer? = Nothing, Optional allowNewCompilerDiagnostics As Boolean = False) - VerifyFix(LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), GetBasicCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics) - End Sub - - ''' - ''' General verifier for codefixes. - ''' Creates a Document from the source string, then gets diagnostics on it And applies the relevant codefixes. - ''' Then gets the string after the codefix Is applied And compares it with the expected result. - ''' Note: If any codefix causes New diagnostics To show up, the test fails unless allowNewCompilerDiagnostics Is Set To True. - ''' - ''' The language the source code Is in - ''' The analyzer to be applied to the source code - ''' The codefix to be applied to the code wherever the relevant Diagnostic Is found - ''' A class in the form of a string before the CodeFix was applied to it - ''' A class in the form of a string after the CodeFix was applied to it - ''' Index determining which codefix to apply if there are multiple - ''' A bool controlling whether Or Not the test will fail if the CodeFix introduces other warnings after being applied - Private Sub VerifyFix(language As String, analyzer As DiagnosticAnalyzer, codeFixProvider As CodeFixProvider, oldSource As String, newSource As String, codeFixIndex As Integer?, allowNewCompilerDiagnostics As Boolean) - - Dim document = CreateDocument(oldSource, language) - Dim analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, New document() {document}) - Dim compilerDiagnostics = GetCompilerDiagnostics(document) - Dim attempts = analyzerDiagnostics.Length - - For i = 0 To attempts - 1 - Dim actions = New List(Of CodeAction)() - Dim context = New CodeFixContext(document, analyzerDiagnostics(0), Sub(a, d) actions.Add(a), CancellationToken.None) - codeFixProvider.RegisterCodeFixesAsync(context).Wait() - - If Not actions.Any() Then - Exit For - End If - - If (codeFixIndex IsNot Nothing) Then - document = ApplyFix(document, actions.ElementAt(codeFixIndex.Value)) - Exit For - End If - - document = ApplyFix(document, actions.ElementAt(0)) - analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, New document() {document}) - - Dim newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)) - - 'check if applying the code fix introduced any New compiler diagnostics - If Not allowNewCompilerDiagnostics AndAlso newCompilerDiagnostics.Any() Then - ' Format And get the compiler diagnostics again so that the locations make sense in the output - document = document.WithSyntaxRoot(Formatter.Format(document.GetSyntaxRootAsync().Result, Formatter.Annotation, document.Project.Solution.Workspace)) - newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)) - - Assert.IsTrue(False, - String.Format("Fix introduced new compiler diagnostics:{2}{0}{2}{2}New document:{2}{1}{2}", - String.Join(vbNewLine, newCompilerDiagnostics.Select(Function(d) d.ToString())), - document.GetSyntaxRootAsync().Result.ToFullString(), vbNewLine)) - End If - - 'check if there are analyzer diagnostics left after the code fix - If Not analyzerDiagnostics.Any() Then - Exit For - End If - Next - - 'after applying all of the code fixes, compare the resulting string to the inputted one - Dim actual = GetStringFromDocument(document) - Assert.AreEqual(newSource, actual) - End Sub - End Class -End Namespace diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticResult.vb b/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticResult.vb deleted file mode 100644 index 8e65996df..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticResult.vb +++ /dev/null @@ -1,83 +0,0 @@ -Imports Microsoft.CodeAnalysis - -Namespace TestHelper - - ''' - ''' Location where the diagnostic appears, as determined by path, line number, And column number. - ''' - Public Structure DiagnosticResultLocation - - Public Sub New(path As String, line As Integer, column As Integer) - If line < -1 Then - Throw New ArgumentOutOfRangeException(NameOf(line), "line must be >= -1") - End If - - If column < -1 Then - Throw New ArgumentOutOfRangeException(NameOf(column), "column must be >= -1") - End If - - Me.Path = path - Me.Line = line - Me.Column = column - - End Sub - - - Public Property Path As String - Public Property Line As Integer - Public Property Column As Integer - - End Structure - - - ''' - ''' Struct that stores information about a Diagnostic appearing in a source - ''' - Public Structure DiagnosticResult - - Private innerlocations As DiagnosticResultLocation() - - Public Property Locations As DiagnosticResultLocation() - Get - - If Me.innerlocations Is Nothing Then - Me.innerlocations = {} - End If - - Return Me.innerlocations - End Get - - Set - - Me.innerlocations = Value - End Set - End Property - - Public Property Severity As DiagnosticSeverity - - Public Property Id As String - - Public Property Message As String - - Public ReadOnly Property Path As String - Get - Return If(Me.Locations.Length > 0, Me.Locations(0).Path, "") - End Get - End Property - - Public ReadOnly Property Line As Integer - Get - Return If(Me.Locations.Length > 0, Me.Locations(0).Line, -1) - End Get - End Property - - Public ReadOnly Property Column As Integer - Get - Return If(Me.Locations.Length > 0, Me.Locations(0).Column, -1) - End Get - End Property - - End Structure - -End Namespace - diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticVerifier.Helper.vb b/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticVerifier.Helper.vb deleted file mode 100644 index c28a20157..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticVerifier.Helper.vb +++ /dev/null @@ -1,160 +0,0 @@ -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.VisualBasic -Imports Microsoft.CodeAnalysis.Diagnostics -Imports Microsoft.CodeAnalysis.Text -Imports System.Collections.Immutable - -Namespace TestHelper - - ' Class for turning strings into documents And getting the diagnostics on them. - ' All methods are Shared. - Partial Public MustInherit Class DiagnosticVerifier - - Private Shared ReadOnly CorlibReference As MetadataReference = MetadataReference.CreateFromFile(GetType(Object).Assembly.Location) - Private Shared ReadOnly SystemCoreReference As MetadataReference = MetadataReference.CreateFromFile(GetType(Enumerable).Assembly.Location) - Private Shared ReadOnly VisualBasicSymbolsReference As MetadataReference = MetadataReference.CreateFromFile(GetType(VisualBasicCompilation).Assembly.Location) - Private Shared ReadOnly CodeAnalysisReference As MetadataReference = MetadataReference.CreateFromFile(GetType(Compilation).Assembly.Location) - - Friend Shared DefaultFilePathPrefix As String = "Test" - Friend Shared CSharpDefaultFileExt As String = "cs" - Friend Shared VisualBasicDefaultExt As String = "vb" - Friend Shared TestProjectName As String = "TestProject" - -#Region " Get Diagnostics " - - ''' - ''' Given classes in the form of strings, their language, And an IDiagnosticAnalyzer to apply to it, return the diagnostics found in the string after converting it to a document. - ''' - ''' Classes in the form of strings - ''' The language the source classes are in - ''' The analyzer to be run on the sources - ''' An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location - Private Shared Function GetSortedDiagnostics(sources As String(), language As String, analyzer As DiagnosticAnalyzer) As Diagnostic() - Return GetSortedDiagnosticsFromDocuments(analyzer, GetDocuments(sources, language)) - End Function - - ''' - ''' Given an analyzer And a document to apply it to, run the analyzer And gather an array of diagnostics found in it. - ''' The returned diagnostics are then ordered by location in the source document. - ''' - ''' The analyzer to run on the documents - ''' The Documents that the analyzer will be run on - ''' An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location - Protected Shared Function GetSortedDiagnosticsFromDocuments(analyzer As DiagnosticAnalyzer, documents As Document()) As Diagnostic() - - Dim projects = New HashSet(Of Project)() - For Each document In documents - projects.Add(document.Project) - Next - - Dim diagnostics = New List(Of Diagnostic)() - For Each project In projects - - Dim compilationWithAnalyzers = project.GetCompilationAsync().Result.WithAnalyzers(ImmutableArray.Create(analyzer)) - Dim diags = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().Result - For Each diag In diags - - If diag.Location = Location.None OrElse diag.Location.IsInMetadata Then - - diagnostics.Add(diag) - Else - - For i = 0 To documents.Length - 1 - - Dim document = documents(i) - Dim tree = document.GetSyntaxTreeAsync().Result - If tree Is diag.Location.SourceTree Then - - diagnostics.Add(diag) - End If - Next - End If - Next - Next - - Dim results = SortDiagnostics(diagnostics) - diagnostics.Clear() - - Return results - End Function - - ''' - ''' Sort diagnostics by location in source document - ''' - ''' The list of Diagnostics to be sorted - ''' An IEnumerable containing the Diagnostics in order of Location - Private Shared Function SortDiagnostics(diagnostics As IEnumerable(Of Diagnostic)) As Diagnostic() - Return diagnostics.OrderBy(Function(d) d.Location.SourceSpan.Start).ToArray() - End Function - -#End Region - -#Region " Set up compilation And documents" - ''' - ''' Given an array of strings as sources And a language, turn them into a project And return the documents And spans of it. - ''' - ''' Classes in the form of strings - ''' The language the source code is in - ''' An array of Documents produced from the source strings - Private Shared Function GetDocuments(sources As String(), language As String) As Document() - - If language <> LanguageNames.CSharp AndAlso language <> LanguageNames.VisualBasic Then - Throw New ArgumentException("Unsupported Language") - End If - - Dim project = CreateProject(sources, language) - Dim documents = project.Documents.ToArray() - - If sources.Length <> documents.Length Then - Throw New SystemException("Amount of sources did not match amount of Documents created") - End If - - Return documents - End Function - - ''' - ''' Create a Document from a string through creating a project that contains it. - ''' - ''' Classes in the form of a string - ''' The language the source code Is in - ''' A Document created from the source string - Protected Shared Function CreateDocument(source As String, Optional language As String = LanguageNames.CSharp) As Document - Return CreateProject({source}, language).Documents.First() - End Function - - ''' - ''' Create a project using the inputted strings as sources. - ''' - ''' Classes in the form of strings - ''' The language the source code is in - ''' A Project created out of the Douments created from the source strings - Private Shared Function CreateProject(sources As String(), Optional language As String = LanguageNames.CSharp) As Project - - Dim fileNamePrefix As String = DefaultFilePathPrefix - Dim fileExt As String = If(language = LanguageNames.CSharp, CSharpDefaultFileExt, VisualBasicDefaultExt) - - Dim projectId As projectId = projectId.CreateNewId(debugName:=TestProjectName) - - Dim solution = New AdhocWorkspace() _ - .CurrentSolution _ - .AddProject(projectId, TestProjectName, TestProjectName, language) _ - .AddMetadataReference(projectId, CorlibReference) _ - .AddMetadataReference(projectId, SystemCoreReference) _ - .AddMetadataReference(projectId, VisualBasicSymbolsReference) _ - .AddMetadataReference(projectId, CodeAnalysisReference) - - Dim count As Integer = 0 - - For Each source In sources - Dim newFileName = fileNamePrefix & count & "." & fileExt - Dim documentId As documentId = documentId.CreateNewId(projectId, debugName:=newFileName) - solution = solution.AddDocument(documentId, newFileName, SourceText.From(source)) - count += 1 - Next - - Return solution.GetProject(projectId) - End Function -#End Region - End Class -End Namespace - diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticVerifier.vb b/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticVerifier.vb deleted file mode 100644 index d9e7a2221..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticVerifier.vb +++ /dev/null @@ -1,303 +0,0 @@ -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Diagnostics -Imports Microsoft.VisualStudio.TestTools.UnitTesting -Imports System.Text - -Namespace TestHelper - ''' Superclass of all Unit Tests for DiagnosticAnalyzers. - Partial Public MustInherit Class DiagnosticVerifier -#Region " To be implemented by Test classes " - ''' - ''' Get the CSharp analyzer being tested - to be implemented in non-abstract class - ''' - Protected Overridable Function GetCSharpDiagnosticAnalyzer() As DiagnosticAnalyzer - Return Nothing - End Function - - ''' - ''' Get the Visual Basic analyzer being tested (C#) - to be implemented in non-abstract class - ''' - Protected Overridable Function GetBasicDiagnosticAnalyzer() As DiagnosticAnalyzer - Return Nothing - End Function -#End Region - -#Region " Verifier wrappers " - - ''' - ''' Called to test a C# DiagnosticAnalyzer when applied on the single inputted string as a source - ''' Note: input a DiagnosticResult For Each Diagnostic expected - ''' - ''' A class in the form of a string to run the analyzer on - ''' DiagnosticResults that should appear after the analyzer Is run on the source - Protected Sub VerifyCSharpDiagnostic(source As String, ParamArray expected As DiagnosticResult()) - - VerifyDiagnostics({source}, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected) - End Sub - - ''' - ''' Called to test a VB DiagnosticAnalyzer when applied on the single inputted string as a source - ''' Note: input a DiagnosticResult For Each Diagnostic expected - ''' - ''' A class in the form of a string to run the analyzer on - ''' DiagnosticResults that should appear after the analyzer Is run on the source - Protected Sub VerifyBasicDiagnostic(source As String, ParamArray expected As DiagnosticResult()) - - VerifyDiagnostics({source}, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected) - End Sub - - ''' - ''' Called to test a C# DiagnosticAnalyzer when applied on the inputted strings as a source - ''' Note: input a DiagnosticResult For Each Diagnostic expected - ''' - ''' An array of strings to create source documents from to run the analyzers on - ''' DiagnosticResults that should appear after the analyzer Is run on the sources - Protected Sub VerifyCSharpDiagnostic(sources As String(), ParamArray expected As DiagnosticResult()) - - VerifyDiagnostics(sources, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected) - End Sub - - ''' - ''' Called to test a VB DiagnosticAnalyzer when applied on the inputted strings as a source - ''' Note: input a DiagnosticResult For Each Diagnostic expected - ''' - ''' An array of strings to create source documents from to run the analyzers on - ''' DiagnosticResults that should appear after the analyzer Is run on the sources - Protected Sub VerifyBasicDiagnostic(sources As String(), ParamArray expected As DiagnosticResult()) - - VerifyDiagnostics(sources, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected) - End Sub - - ''' - ''' General method that gets a collection of actual diagnostics found in the source after the analyzer Is run, - ''' then verifies each of them. - ''' - ''' An array of strings to create source documents from to run the analyzers on - ''' The language of the classes represented by the source strings - ''' The analyzer to be run on the source code - ''' DiagnosticResults that should appear after the analyzer Is run on the sources - Private Sub VerifyDiagnostics(sources As String(), language As String, analyzer As DiagnosticAnalyzer, ParamArray expected As DiagnosticResult()) - - Dim diagnostics = GetSortedDiagnostics(sources, language, analyzer) - VerifyDiagnosticResults(diagnostics, analyzer, expected) - End Sub - -#End Region - -#Region " Actual comparisons And verifications " - ''' - ''' Checks each of the actual Diagnostics found And compares them with the corresponding DiagnosticResult in the array of expected results. - ''' Diagnostics are considered equal only if the DiagnosticResultLocation, Id, Severity, And Message of the DiagnosticResult match the actual diagnostic. - ''' - ''' The Diagnostics found by the compiler after running the analyzer on the source code - ''' The analyzer that was being run on the sources - ''' Diagnostic Results that should have appeared in the code - Private Shared Sub VerifyDiagnosticResults(actualResults As IEnumerable(Of Diagnostic), analyzer As DiagnosticAnalyzer, ParamArray expectedResults As DiagnosticResult()) - - Dim expectedCount = expectedResults.Count() - Dim actualCount = actualResults.Count() - - If expectedCount <> actualCount Then - - Dim diagnosticsOutput = If(actualResults.Any(), FormatDiagnostics(analyzer, actualResults.ToArray()), " NONE.") - - Assert.IsTrue(False, - String.Format( -"Mismatch between number of diagnostics returned, expected ""{0}"" actual ""{1}"" - -Diagnostics: -{2} -", expectedCount, actualCount, diagnosticsOutput)) - End If - - For i = 0 To expectedResults.Length - 1 - - Dim actual = actualResults.ElementAt(i) - Dim expected = expectedResults(i) - - If expected.Line = -1 AndAlso expected.Column = -1 Then - - If (actual.Location <> Location.None) Then - - Assert.IsTrue(False, - String.Format( -"Expected: -A project diagnostic with No location -Actual: -{0}", - FormatDiagnostics(analyzer, actual))) - End If - - Else - - VerifyDiagnosticLocation(analyzer, actual, actual.Location, expected.Locations.First()) - Dim additionalLocations = actual.AdditionalLocations.ToArray() - - If (additionalLocations.Length <> expected.Locations.Length - 1) Then - - Assert.IsTrue(False, - String.Format( -"Expected {0} additional locations but got {1} for Diagnostic: - {2} -", - expected.Locations.Length - 1, additionalLocations.Length, - FormatDiagnostics(analyzer, actual))) - End If - - For j = 0 To additionalLocations.Length - 1 - - VerifyDiagnosticLocation(analyzer, actual, additionalLocations(j), expected.Locations(j + 1)) - Next - End If - - If (actual.Id <> expected.Id) Then - - Assert.IsTrue(False, - String.Format( -"Expected diagnostic id to be ""{0}"" was ""{1}"" - -Diagnostic: - {2} -", - expected.Id, actual.Id, FormatDiagnostics(analyzer, actual))) - End If - - If (actual.Severity <> expected.Severity) Then - - Assert.IsTrue(False, -String.Format( -"Expected diagnostic severity to be ""{0}"" was ""{1}"" - -Diagnostic: - {2} -", - expected.Severity, actual.Severity, FormatDiagnostics(analyzer, actual))) - End If - - If (actual.GetMessage() <> expected.Message) Then - - Assert.IsTrue(False, -String.Format( -"Expected diagnostic message to be ""{0}"" was ""{1}"" - -Diagnostic: - {2} -", - expected.Message, actual.GetMessage(), FormatDiagnostics(analyzer, actual))) - End If - Next - End Sub - - ''' - ''' Helper method to VerifyDiagnosticResult that checks the location of a diagnostic And compares it with the location in the expected DiagnosticResult. - ''' - ''' The analyzer that was being run on the sources - ''' The diagnostic that was found in the code - ''' The Location of the Diagnostic found in the code - ''' The DiagnosticResultLocation that should have been found - Private Shared Sub VerifyDiagnosticLocation(analyzer As DiagnosticAnalyzer, diagnostic As Diagnostic, actual As Location, expected As DiagnosticResultLocation) - - Dim actualSpan = actual.GetLineSpan() - - Assert.IsTrue(actualSpan.Path = expected.Path OrElse (actualSpan.Path IsNot Nothing AndAlso actualSpan.Path.Contains("Test0.") AndAlso expected.Path.Contains("Test.")), - String.Format( -"Expected diagnostic to be in file ""{0}"" was actually in file ""{1}"" - -Diagnostic: - {2} -", - expected.Path, actualSpan.Path, FormatDiagnostics(analyzer, diagnostic))) - - Dim actualLinePosition = actualSpan.StartLinePosition - - ' Only check line position if there Is an actual line in the real diagnostic - If (actualLinePosition.Line > 0) Then - - If (actualLinePosition.Line + 1.0 <> expected.Line) Then - - Assert.IsTrue(False, - String.Format( -"Expected diagnostic to be on line ""{0}"" was actually on line ""{1}"" - -Diagnostic: - {2} -", - expected.Line, actualLinePosition.Line + 1, FormatDiagnostics(analyzer, diagnostic))) - End If - End If - - ' Only check column position if there Is an actual column position in the real diagnostic - If (actualLinePosition.Character > 0) Then - - If (actualLinePosition.Character + 1.0 <> expected.Column) Then - - Assert.IsTrue(False, - String.Format( -"Expected diagnostic to start at column ""{0}"" was actually at column ""{1}"" - -Diagnostic: - {2} -", - expected.Column, actualLinePosition.Character + 1, FormatDiagnostics(analyzer, diagnostic))) - End If - End If - End Sub -#End Region - -#Region " Formatting Diagnostics " - ''' - ''' Helper method to format a Diagnostic into an easily readable string - ''' - ''' The analyzer that this verifier tests - ''' The Diagnostics to be formatted - ''' The Diagnostics formatted as a string - Private Shared Function FormatDiagnostics(analyzer As DiagnosticAnalyzer, ParamArray diagnostics As Diagnostic()) As String - - Dim builder = New StringBuilder() - For i = 0 To diagnostics.Length - 1 - - builder.AppendLine("' " & diagnostics(i).ToString()) - - Dim analyzerType = analyzer.GetType() - Dim rules = analyzer.SupportedDiagnostics - - For Each rule In rules - - If (rule IsNot Nothing AndAlso rule.Id = diagnostics(i).Id) Then - - Dim location = diagnostics(i).Location - If (location = location.None) Then - - builder.AppendFormat("GetGlobalResult({0}.{1})", analyzerType.Name, rule.Id) - Else - - Assert.IsTrue(location.IsInSource, - $"Test base does not currently handle diagnostics in metadata locations. Diagnostic in metadata: {diagnostics(i)} - ") - - Dim resultMethodName As String = If(diagnostics(i).Location.SourceTree.FilePath.EndsWith(".cs"), "GetCSharpResultAt", "GetBasicResultAt") - Dim linePosition = diagnostics(i).Location.GetLineSpan().StartLinePosition - - builder.AppendFormat("{0}({1}, {2}, {3}.{4})", - resultMethodName, - linePosition.Line + 1, - linePosition.Character + 1, - analyzerType.Name, - rule.Id) - End If - - If i <> diagnostics.Length - 1 Then - - builder.Append(","c) - End If - - builder.AppendLine() - Exit For - End If - Next - Next - Return builder.ToString() - End Function -#End Region - End Class -End Namespace diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/Test.vstemplate b/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/Test.vstemplate deleted file mode 100644 index 94dd1e028..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/Test.vstemplate +++ /dev/null @@ -1,33 +0,0 @@ - - - - TestProject - <No description available> - - VisualBasic - 2.0 - 1000 - bb967cab-2ca5-4dac-8809-65b2b28a6351 - true - true - TestProject - true - true - - - - CodeFixVerifier.Helper.vb - DiagnosticResult.vb - DiagnosticVerifier.Helper.vb - UnitTests.vb - CodeFixVerifier.vb - DiagnosticVerifier.vb - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKTestTemplateWizard - - \ No newline at end of file diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/UnitTestProject.vbproj b/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/UnitTestProject.vbproj deleted file mode 100644 index e4f680fd8..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/UnitTestProject.vbproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - netcoreapp2.0 - - - - - - - - - - - - - - diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/UnitTests.vb b/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/UnitTests.vb deleted file mode 100644 index 3adaf44be..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Test/UnitTests.vb +++ /dev/null @@ -1,63 +0,0 @@ -Imports $saferootprojectname$ -Imports $saferootprojectname$.Test.TestHelper -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.CodeFixes -Imports Microsoft.CodeAnalysis.Diagnostics -Imports Microsoft.VisualStudio.TestTools.UnitTesting - -Namespace $safeprojectname$ - - Public Class UnitTest - Inherits CodeFixVerifier - - 'No diagnostics expected to show up - - Public Sub TestMethod1() - Dim test = "" - VerifyBasicDiagnostic(test) - End Sub - - 'Diagnostic And CodeFix both triggered And checked for - - Public Sub TestMethod2() - - Dim test = " -Module Module1 - - Sub Main() - - End Sub - -End Module" - Dim expected = New DiagnosticResult With {.Id = "$saferootidentifiername$", - .Message = String.Format("Type name '{0}' contains lowercase letters", "Module1"), - .Severity = DiagnosticSeverity.Warning, - .Locations = New DiagnosticResultLocation() { - New DiagnosticResultLocation("Test0.vb", 2, 8) - } - } - - - VerifyBasicDiagnostic(test, expected) - - Dim fixtest = " -Module MODULE1 - - Sub Main() - - End Sub - -End Module" - VerifyBasicFix(test, fixtest) - End Sub - - Protected Overrides Function GetBasicCodeFixProvider() As CodeFixProvider - Return New $saferootidentifiername$CodeFixProvider() - End Function - - Protected Overrides Function GetBasicDiagnosticAnalyzer() As DiagnosticAnalyzer - Return New $saferootidentifiername$Analyzer() - End Function - - End Class -End Namespace diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Vsix/AssemblyInfo.vb b/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Vsix/AssemblyInfo.vb deleted file mode 100644 index a0eddd25a..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Vsix/AssemblyInfo.vb +++ /dev/null @@ -1,32 +0,0 @@ -Imports System -Imports System.Reflection -Imports System.Runtime.InteropServices - -' General Information about an assembly is controlled through the following -' set of attributes. Change these attribute values to modify the information -' associated with an assembly. - -' Review the values of the assembly attributes - - - - - - - - - - -' Version information for an assembly consists of the following four values: -' -' Major Version -' Minor Version -' Build Number -' Revision -' -' You can specify all the values or you can default the Build and Revision Numbers -' by using the '*' as shown below: -' - - - diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Vsix/Deployment.vbproj b/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Vsix/Deployment.vbproj deleted file mode 100644 index 401e8d01d..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Vsix/Deployment.vbproj +++ /dev/null @@ -1,79 +0,0 @@ - - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - Debug - AnyCPU - AnyCPU - 2.0 - {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} - $guid1$ - Library - $saferootprojectname$.Vsix - $saferootprojectname$ - v4.6.1 - false - false - false - false - false - false - Roslyn - - - true - full - false - bin\Debug\ - true - true - prompt - $(NoWarn);41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 - - - pdbonly - true - bin\Release\ - false - true - prompt - $(NoWarn);41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 - - - On - - - Binary - - - Off - - - On - - - Program - $(DevEnvDir)devenv.exe - /rootsuffix Roslyn - - - - Designer - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Vsix/Deployment.vstemplate b/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Vsix/Deployment.vstemplate deleted file mode 100644 index 59aad30b4..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Vsix/Deployment.vstemplate +++ /dev/null @@ -1,29 +0,0 @@ - - - - Vsix - <No description available> - - VisualBasic - 2.0 - 1000 - bb967cab-2ca5-4dac-8809-65b2b28a6350 - true - true - Vsix - true - true - - - - source.extension.vsixmanifest - AssemblyInfo.vb - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKVsixTemplateWizardThirdProject - - diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Vsix/source.extension.vsixmanifest b/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Vsix/source.extension.vsixmanifest deleted file mode 100644 index 49e5bb340..000000000 --- a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Vsix/source.extension.vsixmanifest +++ /dev/null @@ -1,22 +0,0 @@ - - - - - $saferootprojectname$ - This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn"). - - - - - - - - - - - - - - - - diff --git a/src/Templates/VS2017/Templates.VisualStudio.2017.csproj b/src/Templates/VS2017/Templates.VisualStudio.2017.csproj deleted file mode 100644 index 848118abf..000000000 --- a/src/Templates/VS2017/Templates.VisualStudio.2017.csproj +++ /dev/null @@ -1,130 +0,0 @@ - - - - net461 - Roslyn.SDK.VS2017 - Roslyn.SDK.VS2017 - false - false - false - false - - Microsoft.CodeAnalysis.SDK - - - - - - - - - - - - - - - Designer - Extensibility - - - Designer - Extensibility - - - Designer - Extensibility - - - - - - Designer - Extensibility - - - Designer - Extensibility - - - Designer - Extensibility - - - - - - Designer - Extensibility - - - Designer - Extensibility - - - Designer - Extensibility - - - - - - Designer - Extensibility - - - Designer - Extensibility - - - Designer - Extensibility - - - - - - SyntaxTree.bmp - true - - - SyntaxTree.ico - true - - - ThirdPartyNotices.rtf - true - - - EULA.rtf - true - - - - - - RoslynSDKTemplateWizard - - - - - SyntaxVisualizerExtension - BuiltProjectOutputGroup%3bGetCopyToOutputDirectoryItems%3bPkgdefProjectOutputGroup - DebugSymbolsProjectOutputGroup%3b - true - - - - - - - <_OriginalVSTemplate Include="@(VSTemplate)" /> - - - - - - - - - \ No newline at end of file diff --git a/src/Templates/VS2017/VBDiagnostic.vstemplate b/src/Templates/VS2017/VBDiagnostic.vstemplate deleted file mode 100644 index 1b54f976f..000000000 --- a/src/Templates/VS2017/VBDiagnostic.vstemplate +++ /dev/null @@ -1,39 +0,0 @@ - - - - Analyzer with Code Fix (.NET Standard) - Create a Visual Basic analyzer that comes with a code fix and generates diagnostics. The analyzer can be deployed as either a NuGet package or a VSIX extension. - CodeInformation.ico - VisualBasic - 2.0 - 1000 - Microsoft.VisualBasic.Analyzer - true - true - Analyzer - true - true - - - - - - ProjectTemplates\VisualBasic\Diagnostic\Analyzer\DiagnosticAnalyzer.vstemplate - - - ProjectTemplates\VisualBasic\Diagnostic\Test\Test.vstemplate - - - ProjectTemplates\VisualBasic\Diagnostic\Vsix\Deployment.vstemplate - - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKRootTemplateWizard - - \ No newline at end of file diff --git a/src/Templates/VS2017/VBRef.vstemplate b/src/Templates/VS2017/VBRef.vstemplate deleted file mode 100644 index 604c44fb9..000000000 --- a/src/Templates/VS2017/VBRef.vstemplate +++ /dev/null @@ -1,36 +0,0 @@ - - - - Code Refactoring (.NET Standard) - Create a Visual Basic refactoring, deployed as a VSIX extension - CodeInformation.ico - VisualBasic - 2.0 - 1000 - Microsoft.VisualBasic.CodeRefactoring - true - true - CodeRefactoring - true - true - - - - - - ProjectTemplates\VisualBasic\CodeRefactoring\Ref\VBCodeRefactoring.vstemplate - - - ProjectTemplates\VisualBasic\CodeRefactoring\Vsix\Deployment.vstemplate - - - - - Roslyn.SDK.Template.Wizard, Version=$(AssemblyVersion), Culture=neutral, PublicKeyToken=31bf3856ad364e35 - RoslynSDKRootTemplateWizard - - \ No newline at end of file diff --git a/src/Templates/VS2017/source.extension.vsixmanifest b/src/Templates/VS2017/source.extension.vsixmanifest deleted file mode 100644 index 74949c583..000000000 --- a/src/Templates/VS2017/source.extension.vsixmanifest +++ /dev/null @@ -1,40 +0,0 @@ - - - - - .NET Compiler Platform SDK For Visual Studio 2017 - The .NET Compiler Platform ("Roslyn") provides open-source C# and Visual Basic compilers with rich code analysis APIs. You can build code analysis tools with the same APIs that Microsoft is using to implement Visual Studio! Also includes the Syntax Visualizer, a Visual Studio extension that allows you to inspect and explore the syntax trees you'll use as you build applications and VS extensions atop the .NET Compiler Platform ("Roslyn"). - Roslyn.SDK.VS2017 - EULA.rtf - roslyn,template,SDK - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Tools/RoslynSDKTemplateWizard/RoslynSDKAnalyzerTemplateWizard.cs b/src/Tools/RoslynSDKTemplateWizard/RoslynSDKAnalyzerTemplateWizard.cs deleted file mode 100644 index a54ccac1d..000000000 --- a/src/Tools/RoslynSDKTemplateWizard/RoslynSDKAnalyzerTemplateWizard.cs +++ /dev/null @@ -1,31 +0,0 @@ -using EnvDTE; -using VSLangProj; - -public class RoslynSDKAnalyzerTemplateWizard : RoslynSDKChildTemplateWizard -{ - public static Project Project { get; private set; } - - public override void OnProjectFinishedGenerating(Project project) - { - // We don't want to copy roslyn binaries to the output folder because they will be - // included in the VSIX. The only way to solve this is to have th wizard mark the - // assemblies as copy local false. - Project = project; - if (project.Object is VSProject vsProject) - { - if (vsProject.References != null) - { - foreach (Reference reference in vsProject.References) - { - if (reference.Name.Contains("Microsoft.CodeAnalysis") || - reference.Name.Contains("System.Collections.Immutable") || - reference.Name.Contains("System.Composition") || - reference.Name.Contains("System.Reflection.Metadata")) - { - reference.CopyLocal = false; - } - } - } - } - } -} diff --git a/src/Tools/RoslynSDKTemplateWizard/RoslynSDKChildTemplateWizard.InterfaceMembers.cs b/src/Tools/RoslynSDKTemplateWizard/RoslynSDKChildTemplateWizard.InterfaceMembers.cs deleted file mode 100644 index 14f43be74..000000000 --- a/src/Tools/RoslynSDKTemplateWizard/RoslynSDKChildTemplateWizard.InterfaceMembers.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using EnvDTE; -using Microsoft.VisualStudio.TemplateWizard; - -public partial class RoslynSDKChildTemplateWizard : IWizard -{ - /// - /// Only one wizard will be run by the project system. - /// This means our wizard needs to load and call the nuget wizard for every function. - /// - private IWizard NugetWizard => lazyWizard.Value; - - private Lazy lazyWizard = new Lazy(() => - { - var asm = Assembly.Load("NuGet.VisualStudio.Interop, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=b03f5f7f11d50a3a"); - return (IWizard)asm.CreateInstance("NuGet.VisualStudio.TemplateWizard"); - }); - - public void BeforeOpeningFile(ProjectItem projectItem) => NugetWizard.BeforeOpeningFile(projectItem); - public void ProjectFinishedGenerating(Project project) - { - NugetWizard.ProjectFinishedGenerating(project); - OnProjectFinishedGenerating(project); - } - public void RunFinished() => NugetWizard.RunFinished(); - public bool ShouldAddProjectItem(string filePath) => NugetWizard.ShouldAddProjectItem(filePath); - public void ProjectItemFinishedGenerating(ProjectItem projectItem) => NugetWizard.ProjectItemFinishedGenerating(projectItem); - public void RunStarted(object automationObject, Dictionary replacementsDictionary, WizardRunKind runKind, object[] customParams) - { - NugetWizard.RunStarted(automationObject, replacementsDictionary, runKind, customParams); - OnRunStarted(automationObject as DTE, replacementsDictionary, runKind, customParams); - } -} diff --git a/src/Tools/RoslynSDKTemplateWizard/RoslynSDKRootTemplateWizard.InterfaceMembers.cs b/src/Tools/RoslynSDKTemplateWizard/RoslynSDKRootTemplateWizard.InterfaceMembers.cs deleted file mode 100644 index 3691ccb6d..000000000 --- a/src/Tools/RoslynSDKTemplateWizard/RoslynSDKRootTemplateWizard.InterfaceMembers.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using EnvDTE; -using Microsoft.VisualStudio.TemplateWizard; - -public partial class RoslynSDKRootTemplateWizard : IWizard -{ - public void BeforeOpeningFile(ProjectItem projectItem) { } - public void ProjectFinishedGenerating(Project project) { } - public void RunFinished() { } - public bool ShouldAddProjectItem(string filePath) => true; - public void ProjectItemFinishedGenerating(ProjectItem projectItem) { } - public void RunStarted(object automationObject, Dictionary replacementsDictionary, WizardRunKind runKind, object[] customParams) - => OnRunStarted(automationObject as DTE, replacementsDictionary, runKind, customParams); -} diff --git a/src/Tools/RoslynSDKTemplateWizard/RoslynSDKRootTemplateWizard.cs b/src/Tools/RoslynSDKTemplateWizard/RoslynSDKRootTemplateWizard.cs deleted file mode 100644 index 483b79999..000000000 --- a/src/Tools/RoslynSDKTemplateWizard/RoslynSDKRootTemplateWizard.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using EnvDTE; -using Microsoft.VisualStudio.TemplateWizard; - -public partial class RoslynSDKRootTemplateWizard -{ - public static Dictionary GlobalDictionary = new Dictionary(); - - private void OnRunStarted(DTE dTE, Dictionary replacementsDictionary, WizardRunKind runKind, object[] customParams) - { - // add the root project name (the name the user passed in) to the global replacement dictionary - GlobalDictionary["$saferootprojectname$"] = replacementsDictionary["$safeprojectname$"]; - GlobalDictionary["$saferootidentifiername$"] = replacementsDictionary["$safeprojectname$"].Replace(".",""); - } -} diff --git a/src/Tools/RoslynSDKTemplateWizard/RoslynSDKTemplateWizard.csproj b/src/Tools/RoslynSDKTemplateWizard/RoslynSDKTemplateWizard.csproj deleted file mode 100644 index 3b6ab869e..000000000 --- a/src/Tools/RoslynSDKTemplateWizard/RoslynSDKTemplateWizard.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - net461 - Roslyn.SDK.Template.Wizard - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Tools/RoslynSDKTemplateWizard/RoslynSDKTestTemplateWizard.cs b/src/Tools/RoslynSDKTemplateWizard/RoslynSDKTestTemplateWizard.cs deleted file mode 100644 index fd476901e..000000000 --- a/src/Tools/RoslynSDKTemplateWizard/RoslynSDKTestTemplateWizard.cs +++ /dev/null @@ -1,14 +0,0 @@ -using EnvDTE; -using VSLangProj; - -public class RoslynSDKTestTemplateWizard : RoslynSDKChildTemplateWizard -{ - public override void OnProjectFinishedGenerating(Project project) - { - // There is no good way for the test project to reference the main project, so we will use the wizard. - if (project.Object is VSProject vsProject) - { - var referenceProject = vsProject.References.AddProject(RoslynSDKAnalyzerTemplateWizard.Project); - } - } -} diff --git a/src/Tools/RoslynSDKTemplateWizard/RoslynSDKVsixTemplateWizardSecondProject.cs b/src/Tools/RoslynSDKTemplateWizard/RoslynSDKVsixTemplateWizardSecondProject.cs deleted file mode 100644 index bb98682e2..000000000 --- a/src/Tools/RoslynSDKTemplateWizard/RoslynSDKVsixTemplateWizardSecondProject.cs +++ /dev/null @@ -1,16 +0,0 @@ -using EnvDTE; - -public class RoslynSDKVsixTemplateWizardSecondProject : RoslynSDKTestTemplateWizard -{ - public override void OnProjectFinishedGenerating(Project project) - { - base.OnProjectFinishedGenerating(project); - - // set the VSIX project to be the starting project - var dte = project.DTE; - if (dte.Solution.Projects.Count == 2) - { - dte.Solution.Properties.Item("StartupProject").Value = project.Name; - } - } -} diff --git a/src/Tools/RoslynSDKTemplateWizard/RoslynSDKVsixTemplateWizardThirdProject.cs b/src/Tools/RoslynSDKTemplateWizard/RoslynSDKVsixTemplateWizardThirdProject.cs deleted file mode 100644 index cfb3a7d1b..000000000 --- a/src/Tools/RoslynSDKTemplateWizard/RoslynSDKVsixTemplateWizardThirdProject.cs +++ /dev/null @@ -1,16 +0,0 @@ -using EnvDTE; - -public class RoslynSDKVsixTemplateWizardThirdProject : RoslynSDKTestTemplateWizard -{ - public override void OnProjectFinishedGenerating(Project project) - { - base.OnProjectFinishedGenerating(project); - - // set the VSIX project to be the starting project - var dte = project.DTE; - if (dte.Solution.Projects.Count == 3) - { - dte.Solution.Properties.Item("StartupProject").Value = project.Name; - } - } -} diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerControl/SyntaxKindHelper.cs b/src/Tools/SyntaxVisualizer/SyntaxVisualizerControl/SyntaxKindHelper.cs deleted file mode 100644 index b476b5f1e..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerControl/SyntaxKindHelper.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.CodeAnalysis; -using CSharpExtensions = Microsoft.CodeAnalysis.CSharp.CSharpExtensions; -using VisualBasicExtensions = Microsoft.CodeAnalysis.VisualBasic.VisualBasicExtensions; - -namespace Roslyn.SyntaxVisualizer.Control -{ - public static class SyntaxKindHelper - { - // Helpers that return the language-specific (C# / VB) SyntaxKind of a language-agnostic - // SyntaxNode / SyntaxToken / SyntaxTrivia. - - public static string GetKind(this SyntaxNodeOrToken nodeOrToken) - { - var kind = string.Empty; - - if (nodeOrToken.IsNode) - { - kind = nodeOrToken.AsNode().GetKind(); - } - else - { - kind = nodeOrToken.AsToken().GetKind(); - } - - return kind; - } - - public static string GetKind(this SyntaxNode node) - { - var kind = string.Empty; - - if (node.Language == LanguageNames.CSharp) - { - kind = CSharpExtensions.Kind(node).ToString(); - } - else - { - kind = VisualBasicExtensions.Kind(node).ToString(); - } - - return kind; - } - - public static string GetKind(this SyntaxToken token) - { - var kind = string.Empty; - - if (token.Language == LanguageNames.CSharp) - { - kind = CSharpExtensions.Kind(token).ToString(); - } - else - { - kind = VisualBasicExtensions.Kind(token).ToString(); - } - - return kind; - } - - public static string GetKind(this SyntaxTrivia trivia) - { - var kind = string.Empty; - - if (trivia.Language == LanguageNames.CSharp) - { - kind = CSharpExtensions.Kind(trivia).ToString(); - } - else - { - kind = VisualBasicExtensions.Kind(trivia).ToString(); - } - - return kind; - } - } -} diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerControl/SyntaxVisualizerControl.csproj b/src/Tools/SyntaxVisualizer/SyntaxVisualizerControl/SyntaxVisualizerControl.csproj deleted file mode 100644 index d83dd3d7c..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerControl/SyntaxVisualizerControl.csproj +++ /dev/null @@ -1,38 +0,0 @@ - - - net461 - Roslyn.SyntaxVisualizer.Control - Roslyn.SyntaxVisualizer.Control - false - - - - - - - - - - - - - - - - - - - - - - - - SyntaxVisualizerControl.xaml - - - MSBuild:Compile - Designer - - - - \ No newline at end of file diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerControl/SyntaxVisualizerControl.xaml b/src/Tools/SyntaxVisualizer/SyntaxVisualizerControl/SyntaxVisualizerControl.xaml deleted file mode 100644 index 7ee75002c..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerControl/SyntaxVisualizerControl.xaml +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerControl/SyntaxVisualizerControl.xaml.cs b/src/Tools/SyntaxVisualizer/SyntaxVisualizerControl/SyntaxVisualizerControl.xaml.cs deleted file mode 100644 index b74641013..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerControl/SyntaxVisualizerControl.xaml.cs +++ /dev/null @@ -1,795 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; - -namespace Roslyn.SyntaxVisualizer.Control -{ - public enum SyntaxCategory - { - None, - SyntaxNode, - SyntaxToken, - SyntaxTrivia - } - - // A control for visually displaying the contents of a SyntaxTree. - public partial class SyntaxVisualizerControl : UserControl - { - // Instances of this class are stored in the Tag field of each item in the treeview. - private class SyntaxTag - { - internal TextSpan Span { get; set; } - internal TextSpan FullSpan { get; set; } - internal TreeViewItem ParentItem { get; set; } - internal string Kind { get; set; } - internal SyntaxNode SyntaxNode { get; set; } - internal SyntaxToken SyntaxToken { get; set; } - internal SyntaxTrivia SyntaxTrivia { get; set; } - internal SyntaxCategory Category { get; set; } - } - - #region Private State - private TreeViewItem _currentSelection; - private bool _isNavigatingFromSourceToTree; - private bool _isNavigatingFromTreeToSource; - private readonly System.Windows.Forms.PropertyGrid _propertyGrid; - private static readonly Thickness s_defaultBorderThickness = new Thickness(1); - #endregion - - #region Public Properties, Events - public SyntaxTree SyntaxTree { get; private set; } - public SemanticModel SemanticModel { get; private set; } - public bool IsLazy { get; private set; } - - public delegate void SyntaxNodeDelegate(SyntaxNode node); - public event SyntaxNodeDelegate SyntaxNodeDirectedGraphRequested; - public event SyntaxNodeDelegate SyntaxNodeNavigationToSourceRequested; - - public delegate void SyntaxTokenDelegate(SyntaxToken token); - public event SyntaxTokenDelegate SyntaxTokenDirectedGraphRequested; - public event SyntaxTokenDelegate SyntaxTokenNavigationToSourceRequested; - - public delegate void SyntaxTriviaDelegate(SyntaxTrivia trivia); - public event SyntaxTriviaDelegate SyntaxTriviaDirectedGraphRequested; - public event SyntaxTriviaDelegate SyntaxTriviaNavigationToSourceRequested; - #endregion - - #region Public Methods - public SyntaxVisualizerControl() - { - InitializeComponent(); - - _propertyGrid = new System.Windows.Forms.PropertyGrid - { - Dock = System.Windows.Forms.DockStyle.Fill, - PropertySort = System.Windows.Forms.PropertySort.Alphabetical, - HelpVisible = false, - ToolbarVisible = false, - CommandsVisibleIfAvailable = false - }; - windowsFormsHost.Child = _propertyGrid; - } - - public void Clear() - { - treeView.Items.Clear(); - _propertyGrid.SelectedObject = null; - typeTextLabel.Visibility = Visibility.Hidden; - kindTextLabel.Visibility = Visibility.Hidden; - typeValueLabel.Content = string.Empty; - kindValueLabel.Content = string.Empty; - legendButton.Visibility = Visibility.Hidden; - } - - // If lazy is true then treeview items are populated on-demand. In other words, when lazy is true - // the children for any given item are only populated when the item is selected. If lazy is - // false then the entire tree is populated at once (and this can result in bad performance when - // displaying large trees). - public void DisplaySyntaxTree(SyntaxTree tree, SemanticModel model = null, bool lazy = true) - { - if (tree != null) - { - IsLazy = lazy; - SyntaxTree = tree; - SemanticModel = model; - AddNode(null, SyntaxTree.GetRoot()); - legendButton.Visibility = Visibility.Visible; - } - } - - // If lazy is true then treeview items are populated on-demand. In other words, when lazy is true - // the children for any given item are only populated when the item is selected. If lazy is - // false then the entire tree is populated at once (and this can result in bad performance when - // displaying large trees). - public void DisplaySyntaxNode(SyntaxNode node, SemanticModel model = null, bool lazy = true) - { - if (node != null) - { - IsLazy = lazy; - SyntaxTree = node.SyntaxTree; - SemanticModel = model; - AddNode(null, node); - legendButton.Visibility = Visibility.Visible; - } - } - - // Select the SyntaxNode / SyntaxToken / SyntaxTrivia whose position best matches the supplied position. - public bool NavigateToBestMatch(int position, string kind = null, - SyntaxCategory category = SyntaxCategory.None, - bool highlightMatch = false, string highlightLegendDescription = null) - { - TreeViewItem match = null; - - if (treeView.HasItems && !_isNavigatingFromTreeToSource) - { - _isNavigatingFromSourceToTree = true; - match = NavigateToBestMatch((TreeViewItem)treeView.Items[0], position, kind, category); - _isNavigatingFromSourceToTree = false; - } - - var matchFound = match != null; - - if (highlightMatch && matchFound) - { - match.Background = Brushes.Yellow; - match.BorderBrush = Brushes.Black; - match.BorderThickness = s_defaultBorderThickness; - highlightLegendTextLabel.Visibility = Visibility.Visible; - highlightLegendDescriptionLabel.Visibility = Visibility.Visible; - if (!string.IsNullOrWhiteSpace(highlightLegendDescription)) - { - highlightLegendDescriptionLabel.Content = highlightLegendDescription; - } - } - - return matchFound; - } - - // Select the SyntaxNode / SyntaxToken / SyntaxTrivia whose span best matches the supplied span. - public bool NavigateToBestMatch(int start, int length, string kind = null, - SyntaxCategory category = SyntaxCategory.None, - bool highlightMatch = false, string highlightLegendDescription = null) - { - return NavigateToBestMatch(new TextSpan(start, length), kind, category, highlightMatch, highlightLegendDescription); - } - - // Select the SyntaxNode / SyntaxToken / SyntaxTrivia whose span best matches the supplied span. - public bool NavigateToBestMatch(TextSpan span, string kind = null, - SyntaxCategory category = SyntaxCategory.None, - bool highlightMatch = false, string highlightLegendDescription = null) - { - TreeViewItem match = null; - - if (treeView.HasItems && !_isNavigatingFromTreeToSource) - { - _isNavigatingFromSourceToTree = true; - match = NavigateToBestMatch((TreeViewItem)treeView.Items[0], span, kind, category); - _isNavigatingFromSourceToTree = false; - } - - var matchFound = match != null; - - if (highlightMatch && matchFound) - { - match.Background = Brushes.Yellow; - match.BorderBrush = Brushes.Black; - match.BorderThickness = s_defaultBorderThickness; - highlightLegendTextLabel.Visibility = Visibility.Visible; - highlightLegendDescriptionLabel.Visibility = Visibility.Visible; - if (!string.IsNullOrWhiteSpace(highlightLegendDescription)) - { - highlightLegendDescriptionLabel.Content = highlightLegendDescription; - } - } - - return matchFound; - } - #endregion - - #region Private Helpers - TreeView Navigation - // Collapse all items in the treeview except for the supplied item. The supplied item - // is also expanded, selected and scrolled into view. - private void CollapseEverythingBut(TreeViewItem item) - { - if (item != null) - { - DeepCollapse((TreeViewItem)treeView.Items[0]); - ExpandPathTo(item); - item.IsSelected = true; - item.BringIntoView(); - } - } - - // Collapse the supplied treeview item including all its descendants. - private void DeepCollapse(TreeViewItem item) - { - if (item != null) - { - item.IsExpanded = false; - foreach (TreeViewItem child in item.Items) - { - DeepCollapse(child); - } - } - } - - // Ensure that the supplied treeview item and all its ancestors are expanded. - private void ExpandPathTo(TreeViewItem item) - { - if (item != null) - { - item.IsExpanded = true; - ExpandPathTo(((SyntaxTag)item.Tag).ParentItem); - } - } - - // Select the SyntaxNode / SyntaxToken / SyntaxTrivia whose position best matches the supplied position. - private TreeViewItem NavigateToBestMatch(TreeViewItem current, int position, string kind = null, - SyntaxCategory category = SyntaxCategory.None) - { - TreeViewItem match = null; - - if (current != null) - { - var currentTag = (SyntaxTag)current.Tag; - if (currentTag.FullSpan.Contains(position)) - { - CollapseEverythingBut(current); - - foreach (TreeViewItem item in current.Items) - { - match = NavigateToBestMatch(item, position, kind, category); - if (match != null) - { - break; - } - } - - if (match == null && (kind == null || currentTag.Kind == kind) && - (category == SyntaxCategory.None || category == currentTag.Category)) - { - match = current; - } - } - } - - return match; - } - - // Select the SyntaxNode / SyntaxToken / SyntaxTrivia whose span best matches the supplied span. - private TreeViewItem NavigateToBestMatch(TreeViewItem current, TextSpan span, string kind = null, - SyntaxCategory category = SyntaxCategory.None) - { - TreeViewItem match = null; - - if (current != null) - { - var currentTag = (SyntaxTag)current.Tag; - if (currentTag.FullSpan.Contains(span)) - { - if ((currentTag.Span == span || currentTag.FullSpan == span) && (kind == null || currentTag.Kind == kind)) - { - CollapseEverythingBut(current); - match = current; - } - else - { - CollapseEverythingBut(current); - - foreach (TreeViewItem item in current.Items) - { - match = NavigateToBestMatch(item, span, kind, category); - if (match != null) - { - break; - } - } - - if (match == null && (kind == null || currentTag.Kind == kind) && - (category == SyntaxCategory.None || category == currentTag.Category)) - { - match = current; - } - } - } - } - - return match; - } - #endregion - - #region Private Helpers - TreeView Population - // Helpers for populating the treeview. - - private void AddNodeOrToken(TreeViewItem parentItem, SyntaxNodeOrToken nodeOrToken) - { - if (nodeOrToken.IsNode) - { - AddNode(parentItem, nodeOrToken.AsNode()); - } - else - { - AddToken(parentItem, nodeOrToken.AsToken()); - } - } - - private void AddNode(TreeViewItem parentItem, SyntaxNode node) - { - var kind = node.GetKind(); - var tag = new SyntaxTag() - { - SyntaxNode = node, - Category = SyntaxCategory.SyntaxNode, - Span = node.Span, - FullSpan = node.FullSpan, - Kind = kind, - ParentItem = parentItem - }; - - var item = new TreeViewItem() - { - Tag = tag, - IsExpanded = false, - Foreground = Brushes.Blue, - Background = node.ContainsDiagnostics ? Brushes.Pink : Brushes.White, - Header = tag.Kind + " " + node.Span.ToString() - }; - - if (SyntaxTree != null && node.ContainsDiagnostics) - { - item.ToolTip = string.Empty; - foreach (var diagnostic in SyntaxTree.GetDiagnostics(node)) - { - item.ToolTip += diagnostic.ToString() + "\n"; - } - - item.ToolTip = item.ToolTip.ToString().Trim(); - } - - item.Selected += new RoutedEventHandler((sender, e) => - { - _isNavigatingFromTreeToSource = true; - - typeTextLabel.Visibility = Visibility.Visible; - kindTextLabel.Visibility = Visibility.Visible; - typeValueLabel.Content = node.GetType().Name; - kindValueLabel.Content = kind; - _propertyGrid.SelectedObject = node; - - item.IsExpanded = true; - - if (!_isNavigatingFromSourceToTree && SyntaxNodeNavigationToSourceRequested != null) - { - SyntaxNodeNavigationToSourceRequested(node); - } - - _isNavigatingFromTreeToSource = false; - e.Handled = true; - }); - - item.Expanded += new RoutedEventHandler((sender, e) => - { - if (item.Items.Count == 1 && item.Items[0] == null) - { - // Remove placeholder child and populate real children. - item.Items.RemoveAt(0); - foreach (var child in node.ChildNodesAndTokens()) - { - AddNodeOrToken(item, child); - } - } - }); - - if (parentItem == null) - { - treeView.Items.Clear(); - treeView.Items.Add(item); - } - else - { - parentItem.Items.Add(item); - } - - if (node.ChildNodesAndTokens().Count > 0) - { - if (IsLazy) - { - // Add placeholder child to indicate that real children need to be populated on expansion. - item.Items.Add(null); - } - else - { - // Recursively populate all descendants. - foreach (var child in node.ChildNodesAndTokens()) - { - AddNodeOrToken(item, child); - } - } - } - } - - private void AddToken(TreeViewItem parentItem, SyntaxToken token) - { - var kind = token.GetKind(); - var tag = new SyntaxTag() - { - SyntaxToken = token, - Category = SyntaxCategory.SyntaxToken, - Span = token.Span, - FullSpan = token.FullSpan, - Kind = kind, - ParentItem = parentItem - }; - - var item = new TreeViewItem() - { - Tag = tag, - IsExpanded = false, - Foreground = Brushes.DarkGreen, - Background = token.ContainsDiagnostics ? Brushes.Pink : Brushes.White, - Header = tag.Kind + " " + token.Span.ToString() - }; - - if (SyntaxTree != null && token.ContainsDiagnostics) - { - item.ToolTip = string.Empty; - foreach (var diagnostic in SyntaxTree.GetDiagnostics(token)) - { - item.ToolTip += diagnostic.ToString() + "\n"; - } - - item.ToolTip = item.ToolTip.ToString().Trim(); - } - - item.Selected += new RoutedEventHandler((sender, e) => - { - _isNavigatingFromTreeToSource = true; - - typeTextLabel.Visibility = Visibility.Visible; - kindTextLabel.Visibility = Visibility.Visible; - typeValueLabel.Content = token.GetType().Name; - kindValueLabel.Content = kind; - _propertyGrid.SelectedObject = token; - - item.IsExpanded = true; - - if (!_isNavigatingFromSourceToTree && SyntaxTokenNavigationToSourceRequested != null) - { - SyntaxTokenNavigationToSourceRequested(token); - } - - _isNavigatingFromTreeToSource = false; - e.Handled = true; - }); - - item.Expanded += new RoutedEventHandler((sender, e) => - { - if (item.Items.Count == 1 && item.Items[0] == null) - { - // Remove placeholder child and populate real children. - item.Items.RemoveAt(0); - foreach (var trivia in token.LeadingTrivia) - { - AddTrivia(item, trivia, true); - } - - foreach (var trivia in token.TrailingTrivia) - { - AddTrivia(item, trivia, false); - } - } - }); - - if (parentItem == null) - { - treeView.Items.Clear(); - treeView.Items.Add(item); - } - else - { - parentItem.Items.Add(item); - } - - if (token.HasLeadingTrivia || token.HasTrailingTrivia) - { - if (IsLazy) - { - // Add placeholder child to indicate that real children need to be populated on expansion. - item.Items.Add(null); - } - else - { - // Recursively populate all descendants. - foreach (var trivia in token.LeadingTrivia) - { - AddTrivia(item, trivia, true); - } - - foreach (var trivia in token.TrailingTrivia) - { - AddTrivia(item, trivia, false); - } - } - } - } - - private void AddTrivia(TreeViewItem parentItem, SyntaxTrivia trivia, bool isLeadingTrivia) - { - var kind = trivia.GetKind(); - var tag = new SyntaxTag() - { - SyntaxTrivia = trivia, - Category = SyntaxCategory.SyntaxTrivia, - Span = trivia.Span, - FullSpan = trivia.FullSpan, - Kind = kind, - ParentItem = parentItem - }; - - var item = new TreeViewItem() - { - Tag = tag, - IsExpanded = false, - Foreground = Brushes.Maroon, - Background = trivia.ContainsDiagnostics ? Brushes.Pink : Brushes.White, - Header = (isLeadingTrivia ? "Lead: " : "Trail: ") + tag.Kind + " " + trivia.Span.ToString() - }; - - if (SyntaxTree != null && trivia.ContainsDiagnostics) - { - item.ToolTip = string.Empty; - foreach (var diagnostic in SyntaxTree.GetDiagnostics(trivia)) - { - item.ToolTip += diagnostic.ToString() + "\n"; - } - - item.ToolTip = item.ToolTip.ToString().Trim(); - } - - item.Selected += new RoutedEventHandler((sender, e) => - { - _isNavigatingFromTreeToSource = true; - - typeTextLabel.Visibility = Visibility.Visible; - kindTextLabel.Visibility = Visibility.Visible; - typeValueLabel.Content = trivia.GetType().Name; - kindValueLabel.Content = kind; - _propertyGrid.SelectedObject = trivia; - - item.IsExpanded = true; - - if (!_isNavigatingFromSourceToTree && SyntaxTriviaNavigationToSourceRequested != null) - { - SyntaxTriviaNavigationToSourceRequested(trivia); - } - - _isNavigatingFromTreeToSource = false; - e.Handled = true; - }); - - item.Expanded += new RoutedEventHandler((sender, e) => - { - if (item.Items.Count == 1 && item.Items[0] == null) - { - // Remove placeholder child and populate real children. - item.Items.RemoveAt(0); - AddNode(item, trivia.GetStructure()); - } - }); - - if (parentItem == null) - { - treeView.Items.Clear(); - treeView.Items.Add(item); - typeTextLabel.Visibility = Visibility.Hidden; - kindTextLabel.Visibility = Visibility.Hidden; - typeValueLabel.Content = string.Empty; - kindValueLabel.Content = string.Empty; - } - else - { - parentItem.Items.Add(item); - } - - if (trivia.HasStructure) - { - if (IsLazy) - { - // Add placeholder child to indicate that real children need to be populated on expansion. - item.Items.Add(null); - } - else - { - // Recursively populate all descendants. - AddNode(item, trivia.GetStructure()); - } - } - } - #endregion - - #region Private Helpers - Other - private void DisplaySymbolInPropertyGrid(ISymbol symbol) - { - if (symbol == null) - { - typeTextLabel.Visibility = Visibility.Hidden; - kindTextLabel.Visibility = Visibility.Hidden; - typeValueLabel.Content = string.Empty; - kindValueLabel.Content = string.Empty; - } - else - { - typeTextLabel.Visibility = Visibility.Visible; - kindTextLabel.Visibility = Visibility.Visible; - typeValueLabel.Content = symbol.GetType().Name; - kindValueLabel.Content = symbol.Kind.ToString(); - } - - _propertyGrid.SelectedObject = symbol; - } - - private static TreeViewItem FindTreeViewItem(DependencyObject source) - { - while (source != null && !(source is TreeViewItem)) - { - source = VisualTreeHelper.GetParent(source); - } - - return (TreeViewItem)source; - } - #endregion - - #region Event Handlers - private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) - { - if (treeView.SelectedItem != null) - { - _currentSelection = (TreeViewItem)treeView.SelectedItem; - } - } - - private void TreeView_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) - { - var item = FindTreeViewItem((DependencyObject)e.OriginalSource); - - if (item != null) - { - item.Focus(); - } - } - - private void TreeView_ContextMenuOpening(object sender, ContextMenuEventArgs e) - { - var directedSyntaxGraphEnabled = - (SyntaxNodeDirectedGraphRequested != null) && - (SyntaxTokenDirectedGraphRequested != null) && - (SyntaxTriviaDirectedGraphRequested != null); - - var symbolDetailsEnabled = - (SemanticModel != null) && - (((SyntaxTag)_currentSelection.Tag).Category == SyntaxCategory.SyntaxNode); - - if ((!directedSyntaxGraphEnabled) && (!symbolDetailsEnabled)) - { - e.Handled = true; - } - else - { - directedSyntaxGraphMenuItem.Visibility = directedSyntaxGraphEnabled ? Visibility.Visible : Visibility.Collapsed; - symbolDetailsMenuItem.Visibility = symbolDetailsEnabled ? Visibility.Visible : Visibility.Collapsed; - typeSymbolDetailsMenuItem.Visibility = symbolDetailsMenuItem.Visibility; - convertedTypeSymbolDetailsMenuItem.Visibility = symbolDetailsMenuItem.Visibility; - aliasSymbolDetailsMenuItem.Visibility = symbolDetailsMenuItem.Visibility; - constantValueDetailsMenuItem.Visibility = symbolDetailsMenuItem.Visibility; - menuItemSeparator1.Visibility = symbolDetailsMenuItem.Visibility; - menuItemSeparator2.Visibility = symbolDetailsMenuItem.Visibility; - } - } - - private void DirectedSyntaxGraphMenuItem_Click(object sender, RoutedEventArgs e) - { - if (_currentSelection != null) - { - var currentTag = (SyntaxTag)_currentSelection.Tag; - - if (currentTag.Category == SyntaxCategory.SyntaxNode && SyntaxNodeDirectedGraphRequested != null) - { - SyntaxNodeDirectedGraphRequested(currentTag.SyntaxNode); - } - else if (currentTag.Category == SyntaxCategory.SyntaxToken && SyntaxTokenDirectedGraphRequested != null) - { - SyntaxTokenDirectedGraphRequested(currentTag.SyntaxToken); - } - else if (currentTag.Category == SyntaxCategory.SyntaxTrivia && SyntaxTriviaDirectedGraphRequested != null) - { - SyntaxTriviaDirectedGraphRequested(currentTag.SyntaxTrivia); - } - } - } - - private void SymbolDetailsMenuItem_Click(object sender, RoutedEventArgs e) - { - var currentTag = (SyntaxTag)_currentSelection.Tag; - if ((SemanticModel != null) && (currentTag.Category == SyntaxCategory.SyntaxNode)) - { - var symbol = SemanticModel.GetSymbolInfo(currentTag.SyntaxNode).Symbol; - if (symbol == null) - { - symbol = SemanticModel.GetDeclaredSymbol(currentTag.SyntaxNode); - } - - if (symbol == null) - { - symbol = SemanticModel.GetPreprocessingSymbolInfo(currentTag.SyntaxNode).Symbol; - } - - DisplaySymbolInPropertyGrid(symbol); - } - } - - private void TypeSymbolDetailsMenuItem_Click(object sender, RoutedEventArgs e) - { - var currentTag = (SyntaxTag)_currentSelection.Tag; - if ((SemanticModel != null) && (currentTag.Category == SyntaxCategory.SyntaxNode)) - { - var symbol = SemanticModel.GetTypeInfo(currentTag.SyntaxNode).Type; - DisplaySymbolInPropertyGrid(symbol); - } - } - - private void ConvertedTypeSymbolDetailsMenuItem_Click(object sender, RoutedEventArgs e) - { - var currentTag = (SyntaxTag)_currentSelection.Tag; - if ((SemanticModel != null) && (currentTag.Category == SyntaxCategory.SyntaxNode)) - { - var symbol = SemanticModel.GetTypeInfo(currentTag.SyntaxNode).ConvertedType; - DisplaySymbolInPropertyGrid(symbol); - } - } - - private void AliasSymbolDetailsMenuItem_Click(object sender, RoutedEventArgs e) - { - var currentTag = (SyntaxTag)_currentSelection.Tag; - if ((SemanticModel != null) && (currentTag.Category == SyntaxCategory.SyntaxNode)) - { - var symbol = SemanticModel.GetAliasInfo(currentTag.SyntaxNode); - DisplaySymbolInPropertyGrid(symbol); - } - } - - private void ConstantValueDetailsMenuItem_Click(object sender, RoutedEventArgs e) - { - var currentTag = (SyntaxTag)_currentSelection.Tag; - if ((SemanticModel != null) && (currentTag.Category == SyntaxCategory.SyntaxNode)) - { - var value = SemanticModel.GetConstantValue(currentTag.SyntaxNode); - kindTextLabel.Visibility = Visibility.Hidden; - kindValueLabel.Content = string.Empty; - - if (!value.HasValue) - { - typeTextLabel.Visibility = Visibility.Hidden; - typeValueLabel.Content = string.Empty; - _propertyGrid.SelectedObject = null; - } - else - { - typeTextLabel.Visibility = Visibility.Visible; - typeValueLabel.Content = value.Value?.GetType().Name ?? ""; - _propertyGrid.SelectedObject = value; - } - } - } - - private void LegendButton_Click(object sender, RoutedEventArgs e) - { - legendPopup.IsOpen = true; - } - #endregion - } -} diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerDgmlHelper/ObjectInfoHelper.vb b/src/Tools/SyntaxVisualizer/SyntaxVisualizerDgmlHelper/ObjectInfoHelper.vb deleted file mode 100644 index 1e05f5166..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerDgmlHelper/ObjectInfoHelper.vb +++ /dev/null @@ -1,162 +0,0 @@ -' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -Imports System -Imports System.Collections.Generic -Imports System.Linq -Imports Microsoft.CodeAnalysis - -Friend Module ObjectInfoHelper - 'Helpers that use Reflection to return details pertaining to an object including - 'its type + the types, names and values of properties on this object. - -#Region "GetObjectInfo" - Friend Function GetObjectInfo(nodeOrToken As SyntaxNodeOrToken) As ObjectInfo - Dim info As ObjectInfo = Nothing - - If nodeOrToken.IsNode Then - info = GetObjectInfo(nodeOrToken.AsNode) - Else - info = GetObjectInfo(nodeOrToken.AsToken) - End If - - Return info - End Function - - Friend Function GetObjectInfo(node As SyntaxNode) As ObjectInfo - Dim type = node.GetType() - - Dim properties = type.GetProperties(System.Reflection.BindingFlags.Instance Or - System.Reflection.BindingFlags.Public) - Dim propertyInfos = (From p In properties Where IsSimpleProperty(p) - Select GetPropertyInfo(p, node)).ToList() - - Return New ObjectInfo(type.Name, propertyInfos) - End Function - - Friend Function GetObjectInfo(token As SyntaxToken) As ObjectInfo - Dim type = token.GetType() - - Dim properties = type.GetProperties(System.Reflection.BindingFlags.Instance Or - System.Reflection.BindingFlags.Public) - Dim propertyInfos = (From p In properties Where IsSimpleProperty(p) - Select GetPropertyInfo(p, token)).ToList() - - Return New ObjectInfo(type.Name, propertyInfos) - End Function - - Friend Function GetObjectInfo(trivia As SyntaxTrivia) As ObjectInfo - Dim type = trivia.GetType() - - Dim properties = type.GetProperties(System.Reflection.BindingFlags.Instance Or - System.Reflection.BindingFlags.Public) - Dim propertyInfos = (From p In properties Where IsSimpleProperty(p) - Select GetPropertyInfo(p, trivia)).ToList() - Return New ObjectInfo(type.Name, propertyInfos) - End Function -#End Region - -#Region "GetPropertyInfo" - Private Function IsSimpleProperty(prop As System.Reflection.PropertyInfo) As Boolean - Dim type = prop.PropertyType - If type Is GetType(Char) OrElse - type Is GetType(Boolean) OrElse - type Is GetType(Short) OrElse - type Is GetType(UShort) OrElse - type Is GetType(Integer) OrElse - type Is GetType(UInteger) OrElse - type Is GetType(Long) OrElse - type Is GetType(ULong) OrElse - type Is GetType(Single) OrElse - type Is GetType(Double) OrElse - type Is GetType(Date) OrElse - type Is GetType(Decimal) OrElse - type Is GetType(String) OrElse - type.IsEnum Then - Return True - Else - Return False - End If - End Function - - 'Only called if IsSimpleProperty returns true. - Private Function GetPropertyInfo(prop As System.Reflection.PropertyInfo, - node As SyntaxNode) As ObjectInfo.PropertyInfo - Return New ObjectInfo.PropertyInfo(prop.Name, prop.PropertyType, - prop.GetValue(node, Nothing)) - End Function - - 'Only called if IsSimpleProperty returns true. - Private Function GetPropertyInfo(prop As System.Reflection.PropertyInfo, - token As SyntaxToken) As ObjectInfo.PropertyInfo - Return New ObjectInfo.PropertyInfo(prop.Name, prop.PropertyType, - prop.GetValue(token, Nothing)) - End Function - - 'Only called if IsSimpleProperty returns true. - Private Function GetPropertyInfo(prop As System.Reflection.PropertyInfo, - trivia As SyntaxTrivia) As ObjectInfo.PropertyInfo - Return New ObjectInfo.PropertyInfo(prop.Name, prop.PropertyType, - prop.GetValue(trivia, Nothing)) - End Function -#End Region -End Module - -'Encapsulates details pertaining to an object including its type + the types, names -'and values of properties on this object. -Friend Class ObjectInfo - Private ReadOnly _typeName As String - Private ReadOnly _propertyInfos As IEnumerable(Of PropertyInfo) - Private Shared ReadOnly s_emptyPropertyInfos As IEnumerable(Of PropertyInfo) = Array.Empty(Of PropertyInfo) - - Friend ReadOnly Property TypeName As String - Get - Return _typeName - End Get - End Property - - Friend ReadOnly Property PropertyInfos As IEnumerable(Of PropertyInfo) - Get - If _propertyInfos Is Nothing Then - Return s_emptyPropertyInfos - Else - Return _propertyInfos - End If - End Get - End Property - - Friend Sub New(typeName As String, propertyInfos As IEnumerable(Of PropertyInfo)) - _typeName = typeName - _propertyInfos = propertyInfos - End Sub - - 'Encapsulates the name, type and value of a property on an object. - Friend Class PropertyInfo - Private ReadOnly _name As String - Private ReadOnly _type As Type - Private ReadOnly _value As Object - - Friend ReadOnly Property Name As String - Get - Return _name - End Get - End Property - - Friend ReadOnly Property Type As Type - Get - Return _type - End Get - End Property - - Friend ReadOnly Property Value As Object - Get - Return _value - End Get - End Property - - Friend Sub New(name As String, type As Type, value As Object) - _name = name - _type = type - _value = value - End Sub - End Class -End Class diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerDgmlHelper/SyntaxDgmlHelper.vb b/src/Tools/SyntaxVisualizer/SyntaxVisualizerDgmlHelper/SyntaxDgmlHelper.vb deleted file mode 100644 index 461b1c5be..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerDgmlHelper/SyntaxDgmlHelper.vb +++ /dev/null @@ -1,495 +0,0 @@ -' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -Imports System.Collections.Generic -Imports System.Linq -Imports System.Runtime.CompilerServices -Imports System.Xml.Linq -Imports Microsoft.CodeAnalysis -Imports Microsoft.VisualBasic -Imports - -Public Class SyntaxDgmlOptions - Public Property ShowTrivia As Boolean = True - Public Property ShowSpan As Boolean = True - Public Property ShowErrors As Boolean = True - Public Property ShowText As Boolean = False - Public Property ShowGroups As Boolean = False -End Class - -Public Module SyntaxDgmlHelper - Private ReadOnly s_defaultOptions As New SyntaxDgmlOptions - Private Const s_MAX_LABEL_LENGTH = 30 - - 'Helpers that return the DGML representation of a SyntaxNode / SyntaxToken / SyntaxTrivia. - 'DGML is an XML-based format for directed graphs that can be rendered by Visual Studio. - -#Region "ToDgml" - - Public Function ToDgml(nodeOrToken As SyntaxNodeOrToken, - Optional options As SyntaxDgmlOptions = Nothing) As XElement - Dim dgml As XElement = Nothing - - If nodeOrToken.IsNode Then - dgml = ToDgml(nodeOrToken.AsNode, options) - Else - dgml = ToDgml(nodeOrToken.AsToken, options) - End If - - Return dgml - End Function - - - Public Function ToDgml(node As SyntaxNode, - Optional options As SyntaxDgmlOptions = Nothing) As XElement - If options Is Nothing Then - options = s_defaultOptions - End If - - Dim dgml = GetDgmlTemplate(options) - ProcessNode(options, node, dgml) - Return dgml - End Function - - - Public Function ToDgml(token As SyntaxToken, - Optional options As SyntaxDgmlOptions = Nothing) As XElement - If options Is Nothing Then - options = s_defaultOptions - End If - - Dim dgml = GetDgmlTemplate(options) - ProcessToken(options, token, dgml) - Return dgml - End Function - - - Public Function ToDgml(trivia As SyntaxTrivia, - Optional options As SyntaxDgmlOptions = Nothing) As XElement - If options Is Nothing Then - options = s_defaultOptions - End If - - Dim dgml = GetDgmlTemplate(options) - ProcessTrivia(options, trivia, dgml) - Return dgml - End Function -#End Region - -#Region "Process*" - Private Sub ProcessNodeOrToken(options As SyntaxDgmlOptions, nodeOrToken As SyntaxNodeOrToken, dgml As XElement, - Optional ByRef count As Integer = 0, - Optional parent As XElement = Nothing, - Optional parentGroup As XElement = Nothing, - Optional properties As HashSet(Of String) = Nothing) - If nodeOrToken.IsNode Then - ProcessNode(options, nodeOrToken.AsNode, dgml, count, parent, parentGroup, properties) - Else - ProcessToken(options, nodeOrToken.AsToken, dgml, count, parent, parentGroup, properties) - End If - End Sub - - Private Sub ProcessNode(options As SyntaxDgmlOptions, node As SyntaxNode, dgml As XElement, - Optional ByRef count As Integer = 0, - Optional parent As XElement = Nothing, - Optional parentGroup As XElement = Nothing, - Optional properties As HashSet(Of String) = Nothing) - count += 1 - - Dim current = Label=<%= GetLabelForNode(node) %>/> - Dim currentID = count, parentID = -1, currentGroupID = -1, parentGroupID = -1 - Initialize(options, dgml, parent, parentGroup, current, properties, currentID, parentID, currentGroupID, parentGroupID) - AddNodeInfo(options, node, current, dgml, properties) - Dim currentGroup As XElement = parentGroup - - current.@Category = "0" - - If options.ShowGroups Then - count += 1 - currentGroup = Label=<%= GetLabelForNode(node) %>/> - AddNodeInfo(options, node, currentGroup, dgml, properties) - dgml..First.Add(currentGroup) - currentGroupID = count - dgml..First.Add( Target=<%= currentID %> Category="7">) - If parentGroupID <> -1 Then - dgml..First.Add( Target=<%= currentGroupID %> Category="7">) - End If - End If - - Dim kind = node.GetKind() - - If (node.IsMissing OrElse node.Span.Length = 0) AndAlso Not kind = "CompilationUnit" Then - current.@Category = "4" - End If - - If kind.Contains("Bad") OrElse kind.Contains("Skipped") Then - current.@Category = "5" - End If - - If options.ShowErrors AndAlso node.ContainsDiagnostics Then - AddErrorIcon(current) - End If - - For Each childSyntaxNode In node.ChildNodesAndTokens() - ProcessNodeOrToken(options, childSyntaxNode, dgml, count, current, currentGroup, properties) - Next - End Sub - - Private Sub ProcessToken(options As SyntaxDgmlOptions, token As SyntaxToken, dgml As XElement, - Optional ByRef count As Integer = 0, - Optional parent As XElement = Nothing, - Optional parentGroup As XElement = Nothing, - Optional properties As HashSet(Of String) = Nothing) - count += 1 - - Dim current = Label=<%= GetLabelForToken(token) %>/> - Initialize(options, dgml, parent, parentGroup, current, properties, count, 0, 0, 0) - AddTokenInfo(options, token, current, dgml, properties) - Dim currentGroup As XElement = parentGroup - - current.@Category = "1" - - Dim kind = token.GetKind() - - If (token.IsMissing OrElse token.Span.Length = 0) AndAlso Not kind = "EndOfFileToken" Then - current.@Category = "4" - End If - - If kind.Contains("Bad") OrElse kind.Contains("Skipped") Then - current.@Category = "5" - End If - - If options.ShowErrors AndAlso token.ContainsDiagnostics Then - AddErrorIcon(current) - End If - - If options.ShowTrivia Then - For Each triviaNode In token.LeadingTrivia - ProcessTrivia(options, triviaNode, dgml, count, True, current, currentGroup, properties) - Next - For Each triviaNode In token.TrailingTrivia - ProcessTrivia(options, triviaNode, dgml, count, False, current, currentGroup, properties) - Next - End If - End Sub - - Private Sub ProcessTrivia(options As SyntaxDgmlOptions, trivia As SyntaxTrivia, dgml As XElement, - Optional ByRef count As Integer = 0, - Optional isProcessingLeadingTrivia As Boolean = False, - Optional parent As XElement = Nothing, - Optional parentGroup As XElement = Nothing, - Optional properties As HashSet(Of String) = Nothing) - count += 1 - - Dim current = Label=<%= GetLabelForTrivia(trivia) %>/> - Initialize(options, dgml, parent, parentGroup, current, properties, count, 0, 0, 0) - AddTriviaInfo(options, trivia, current, dgml, properties) - Dim currentGroup As XElement = parentGroup - - If isProcessingLeadingTrivia Then - current.@Category = "2" - Else - current.@Category = "3" - End If - - Dim kind = trivia.GetKind() - - If kind.Contains("Bad") OrElse kind.Contains("Skipped") Then - current.@Category = "5" - End If - - If options.ShowErrors AndAlso trivia.ContainsDiagnostics Then - AddErrorIcon(current) - End If - - If options.ShowTrivia Then - If trivia.HasStructure Then - ProcessNode(options, trivia.GetStructure, dgml, count, current, currentGroup, properties) - End If - End If - End Sub -#End Region - -#Region "GetLabel*" - Private Function GetLabelForNode(node As SyntaxNode) As String - Return node.GetKind() - End Function - - Private Function GetLabelForToken(token As SyntaxToken) As String - Dim label = token.GetKind() - Dim text = token.ToString() - - If text.Trim <> String.Empty Then - If text.Length <= s_MAX_LABEL_LENGTH Then - label = text - Else - label = text.Remove(s_MAX_LABEL_LENGTH) & "..." - End If - End If - - Return label - End Function - - Private Function GetLabelForTrivia(trivia As SyntaxTrivia) As String - Return trivia.GetKind() - End Function -#End Region - -#Region "Add*" - Private Sub AddNodeInfo(options As SyntaxDgmlOptions, node As SyntaxNode, - current As XElement, dgml As XElement, - properties As HashSet(Of String)) - Dim nodeInfo = GetObjectInfo(node) - AddDgmlProperty("Type", properties, dgml) - current.@Type = nodeInfo.TypeName - AddDgmlProperty("Kind", properties, dgml) - current.@Kind = node.GetKind() - - If options.ShowSpan Then - AddDgmlProperty("Span", properties, dgml) - current.@Span = String.Format("{0} Length: {1}", - node.Span.ToString, - node.Span.Length) - AddDgmlProperty("FullSpan", properties, dgml) - current.@FullSpan = String.Format("{0} Length: {1}", - node.FullSpan.ToString, - node.FullSpan.Length) - End If - - For Each field In nodeInfo.PropertyInfos - Dim name = field.Name - If Not (name.Contains("Span") OrElse name.Contains("Kind") OrElse name.Contains("Text")) Then - AddDgmlProperty(name, properties, dgml) - current.Add(New XAttribute(name, field.Value.ToString)) - End If - Next - - Dim syntaxTree = node.SyntaxTree - If syntaxTree IsNot Nothing AndAlso options.ShowErrors Then - Dim syntaxErrors = syntaxTree.GetDiagnostics(node) - AddDgmlProperty("Errors", properties, dgml) - current.@Errors = String.Format("Count: {0}", syntaxErrors.Count) - For Each syntaxError In syntaxErrors - current.@Errors &= vbCrLf & syntaxError.ToString(Nothing) - Next - End If - - If options.ShowText Then - AddDgmlProperty("Text", properties, dgml) - current.@Text = node.ToString() - AddDgmlProperty("FullText", properties, dgml) - current.@FullText = node.ToFullString() - End If - End Sub - - Private Sub AddTokenInfo(options As SyntaxDgmlOptions, token As SyntaxToken, - current As XElement, dgml As XElement, - properties As HashSet(Of String)) - Dim tokenInfo = GetObjectInfo(token) - AddDgmlProperty("Type", properties, dgml) - current.@Type = tokenInfo.TypeName - AddDgmlProperty("Kind", properties, dgml) - current.@Kind = token.GetKind() - - If options.ShowSpan Then - AddDgmlProperty("Span", properties, dgml) - current.@Span = String.Format("{0} Length: {1}", - token.Span.ToString, - token.Span.Length) - AddDgmlProperty("FullSpan", properties, dgml) - current.@FullSpan = String.Format("{0} Length: {1}", - token.FullSpan.ToString, - token.FullSpan.Length) - End If - - For Each field In tokenInfo.PropertyInfos - Dim name = field.Name - If Not (name.Contains("Span") OrElse name.Contains("Kind") OrElse name.Contains("Text")) Then - AddDgmlProperty(name, properties, dgml) - current.Add(New XAttribute(name, field.Value.ToString)) - End If - Next - - Dim syntaxTree = token.SyntaxTree - If syntaxTree IsNot Nothing AndAlso options.ShowErrors Then - Dim syntaxErrors = syntaxTree.GetDiagnostics(token) - AddDgmlProperty("Errors", properties, dgml) - current.@Errors = String.Format("Count: {0}", syntaxErrors.Count) - For Each syntaxError In syntaxErrors - current.@Errors &= vbCrLf & syntaxError.ToString(Nothing) - Next - End If - - If options.ShowText Then - AddDgmlProperty("Text", properties, dgml) - current.@Text = token.ToString() - AddDgmlProperty("FullText", properties, dgml) - current.@FullText = token.ToFullString() - End If - End Sub - - Private Sub AddTriviaInfo(options As SyntaxDgmlOptions, trivia As SyntaxTrivia, - current As XElement, dgml As XElement, - properties As HashSet(Of String)) - Dim triviaInfo = GetObjectInfo(trivia) - AddDgmlProperty("Type", properties, dgml) - current.@Type = triviaInfo.TypeName - AddDgmlProperty("Kind", properties, dgml) - current.@Kind = trivia.GetKind() - - If options.ShowSpan Then - AddDgmlProperty("Span", properties, dgml) - current.@Span = String.Format("{0} Length: {1}", - trivia.Span.ToString, - trivia.Span.Length) - AddDgmlProperty("FullSpan", properties, dgml) - current.@FullSpan = String.Format("{0} Length: {1}", - trivia.FullSpan.ToString, - trivia.FullSpan.Length) - End If - - For Each field In triviaInfo.PropertyInfos - Dim name = field.Name - If Not (name.Contains("Span") OrElse name.Contains("Kind") OrElse name.Contains("Text")) Then - AddDgmlProperty(name, properties, dgml) - current.Add(New XAttribute(name, field.Value.ToString)) - End If - Next - - Dim syntaxTree = trivia.SyntaxTree - If syntaxTree IsNot Nothing AndAlso options.ShowErrors Then - Dim syntaxErrors = syntaxTree.GetDiagnostics(trivia) - AddDgmlProperty("Errors", properties, dgml) - current.@Errors = String.Format("Count: {0}", syntaxErrors.Count) - For Each syntaxError In syntaxErrors - current.@Errors &= vbCrLf & syntaxError.ToString(Nothing) - Next - End If - - If options.ShowText Then - AddDgmlProperty("Text", properties, dgml) - current.@Text = trivia.ToString() - AddDgmlProperty("FullText", properties, dgml) - current.@FullText = trivia.ToFullString() - End If - End Sub -#End Region - -#Region "Other Helpers" - Private Sub Initialize(options As SyntaxDgmlOptions, - dgml As XElement, - parent As XElement, - parentGroup As XElement, - current As XElement, - ByRef properties As HashSet(Of String), - currentID As Integer, - ByRef parentID As Integer, - ByRef currentGroupID As Integer, - ByRef parentGroupID As Integer) - dgml..First.Add(current) - - parentGroupID = -1 : currentGroupID = -1 - parentID = -1 - - If parent IsNot Nothing Then - parentID = CInt(parent.@Id) - End If - - If options.ShowGroups Then - If parentGroup IsNot Nothing Then - parentGroupID = CInt(parentGroup.@Id) - End If - currentGroupID = parentGroupID - End If - - If parentID <> -1 Then - dgml..First.Add( Target=<%= currentID %>>) - End If - - If options.ShowGroups AndAlso parentGroupID <> -1 Then - dgml..First.Add( Target=<%= currentID %> Category="7">) - End If - - If properties Is Nothing Then - properties = New HashSet(Of String) - End If - End Sub - - Private Function GetDgmlTemplate(options As SyntaxDgmlOptions) As XElement - Dim dgml = - - - - - - - - - - - - - - - - - - - <%= If(options.ShowTrivia, - , Nothing) %> - <%= If(options.ShowTrivia, - , Nothing) %> - - - - - - - dgml.AddAnnotation(SaveOptions.OmitDuplicateNamespaces) - - If options.ShowGroups Then - dgml..First.Add() - End If - Return dgml - End Function - - Private Sub AddDgmlProperty(propertyName As String, properties As HashSet(Of String), dgml As XElement) - If Not properties.Contains(propertyName) Then - dgml..First.Add( Label=<%= propertyName %> DataType="System.String"/>) - properties.Add(propertyName) - End If - End Sub - - Private Sub AddErrorIcon(element As XElement) - element.@Icon = "CodeSchema_Event" - End Sub -#End Region -End Module diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerDgmlHelper/SyntaxKindHelper.vb b/src/Tools/SyntaxVisualizer/SyntaxVisualizerDgmlHelper/SyntaxKindHelper.vb deleted file mode 100644 index e8645ef9c..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerDgmlHelper/SyntaxKindHelper.vb +++ /dev/null @@ -1,61 +0,0 @@ -' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -Imports System.Runtime.CompilerServices -Imports Microsoft.CodeAnalysis - -Friend Module SyntaxKindHelper - 'Helpers that return the language-specific (C# / VB) SyntaxKind of a language-agnostic - 'SyntaxNode / SyntaxToken / SyntaxTrivia. - - - Friend Function GetKind(nodeOrToken As SyntaxNodeOrToken) As String - Dim kind = String.Empty - - If nodeOrToken.IsNode Then - kind = nodeOrToken.AsNode().GetKind() - Else - kind = nodeOrToken.AsToken().GetKind() - End If - - Return kind - End Function - - - Friend Function GetKind(node As SyntaxNode) As String - Dim kind = String.Empty - - If node.Language = LanguageNames.CSharp Then - kind = CSharp.CSharpExtensions.Kind(node).ToString() - Else - kind = VisualBasic.VisualBasicExtensions.Kind(node).ToString() - End If - - Return kind - End Function - - - Friend Function GetKind(token As SyntaxToken) As String - Dim kind = String.Empty - - If token.Language = LanguageNames.CSharp Then - kind = CSharp.CSharpExtensions.Kind(token).ToString() - Else - kind = VisualBasic.VisualBasicExtensions.Kind(token).ToString() - End If - - Return kind - End Function - - - Friend Function GetKind(trivia As SyntaxTrivia) As String - Dim kind = String.Empty - - If trivia.Language = LanguageNames.CSharp Then - kind = CSharp.CSharpExtensions.Kind(trivia).ToString() - Else - kind = VisualBasic.VisualBasicExtensions.Kind(trivia).ToString() - End If - - Return kind - End Function -End Module diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerDgmlHelper/SyntaxVisualizerDgmlHelper.vbproj b/src/Tools/SyntaxVisualizer/SyntaxVisualizerDgmlHelper/SyntaxVisualizerDgmlHelper.vbproj deleted file mode 100644 index b72237d66..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerDgmlHelper/SyntaxVisualizerDgmlHelper.vbproj +++ /dev/null @@ -1,24 +0,0 @@ - - - net461 - Roslyn.SyntaxVisualizer.DgmlHelper - Roslyn.SyntaxVisualizer.DgmlHelper - false - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/PkgCmdIDList.cs b/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/PkgCmdIDList.cs deleted file mode 100644 index bdbcad578..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/PkgCmdIDList.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Roslyn.SyntaxVisualizer.Extension -{ - internal static class PkgCmdIDList - { - internal const uint CmdIDRoslynSyntaxVisualizer = 0x101; - } -} diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/SyntaxVisualizerContainer.xaml.cs b/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/SyntaxVisualizerContainer.xaml.cs deleted file mode 100644 index 4f3b187de..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/SyntaxVisualizerContainer.xaml.cs +++ /dev/null @@ -1,619 +0,0 @@ -using System; -using System.IO; -using System.Runtime.InteropServices; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Threading; -using System.Xml.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.ComponentModelHost; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Text.Editor; - -namespace Roslyn.SyntaxVisualizer.Extension -{ - // Control that hosts SyntaxVisualizerControl inside a Visual Studio ToolWindow. This control implements all the - // logic necessary for interaction with Visual Studio's code documents and directed graph documents. - internal partial class SyntaxVisualizerContainer : UserControl, IVsRunningDocTableEvents, IVsSolutionEvents, IDisposable - { - private SyntaxVisualizerToolWindow parent; - private IWpfTextView activeWpfTextView; - private SyntaxTree activeSyntaxTree; - private DispatcherTimer typingTimer; - - private const string CSharpContentType = "CSharp"; - private const string VisualBasicContentType = "Basic"; - private readonly TimeSpan typingTimerTimeout = TimeSpan.FromMilliseconds(300); - - internal SyntaxVisualizerContainer(SyntaxVisualizerToolWindow parent) - { - InitializeComponent(); - - this.parent = parent; - - InitializeRunningDocumentTable(); - - var shellService = GetService(GlobalServiceProvider); - if (shellService != null) - { - - // Only enable this feature if the Visual Studio package for DGML is installed. - shellService.IsPackageInstalled(GuidList.GuidProgressionPkg, out var canDisplayDirectedSyntaxGraph); - if (Convert.ToBoolean(canDisplayDirectedSyntaxGraph)) - { - syntaxVisualizer.SyntaxNodeDirectedGraphRequested += DisplaySyntaxNodeDgml; - syntaxVisualizer.SyntaxTokenDirectedGraphRequested += DisplaySyntaxTokenDgml; - syntaxVisualizer.SyntaxTriviaDirectedGraphRequested += DisplaySyntaxTriviaDgml; - } - } - - syntaxVisualizer.SyntaxNodeNavigationToSourceRequested += node => NavigateToSource(node.Span); - syntaxVisualizer.SyntaxTokenNavigationToSourceRequested += token => NavigateToSource(token.Span); - syntaxVisualizer.SyntaxTriviaNavigationToSourceRequested += trivia => NavigateToSource(trivia.Span); - } - - internal void Clear() - { - if (typingTimer != null) - { - typingTimer.Stop(); - typingTimer.Tick -= HandleTypingTimerTimeout; - typingTimer = null; - } - - if (activeWpfTextView != null) - { - activeWpfTextView.Selection.SelectionChanged -= HandleSelectionChanged; - activeWpfTextView.TextBuffer.Changed -= HandleTextBufferChanged; - activeWpfTextView.LostAggregateFocus -= HandleTextViewLostFocus; - activeWpfTextView = null; - } - - activeSyntaxTree = null; - syntaxVisualizer.Clear(); - } - - #region Helpers - GetService - private static Microsoft.VisualStudio.OLE.Interop.IServiceProvider globalServiceProvider; - private static Microsoft.VisualStudio.OLE.Interop.IServiceProvider GlobalServiceProvider - { - get - { - if (globalServiceProvider == null) - { - globalServiceProvider = (Microsoft.VisualStudio.OLE.Interop.IServiceProvider)Package.GetGlobalService( - typeof(Microsoft.VisualStudio.OLE.Interop.IServiceProvider)); - } - - return globalServiceProvider; - } - } - - private TServiceInterface GetService() - where TServiceInterface : class - where TService : class - { - TServiceInterface service = null; - - if (parent != null) - { - service = parent.GetVsService(); - } - - return service; - } - - private static object GetService( - Microsoft.VisualStudio.OLE.Interop.IServiceProvider serviceProvider, Guid guidService, bool unique) - { - var guidInterface = VSConstants.IID_IUnknown; - var ptr = IntPtr.Zero; - object service = null; - - if (serviceProvider.QueryService(ref guidService, ref guidInterface, out ptr) == 0 && - ptr != IntPtr.Zero) - { - try - { - if (unique) - { - service = Marshal.GetUniqueObjectForIUnknown(ptr); - } - else - { - service = Marshal.GetObjectForIUnknown(ptr); - } - } - finally - { - Marshal.Release(ptr); - } - } - - return service; - } - - private static TServiceInterface GetService( - Microsoft.VisualStudio.OLE.Interop.IServiceProvider serviceProvider) - where TServiceInterface : class - where TService : class - { - return (TServiceInterface)GetService(serviceProvider, typeof(TService).GUID, false); - } - - private static TServiceInterface GetMefService() where TServiceInterface : class - { - TServiceInterface service = null; - var componentModel = GetService(GlobalServiceProvider); - - if (componentModel != null) - { - service = componentModel.GetService(); - } - - return service; - } - #endregion - - #region Helpers - Initialize and Dispose IVsRunningDocumentTable - private uint runningDocumentTableCookie; - - private IVsRunningDocumentTable runningDocumentTable; - private IVsRunningDocumentTable RunningDocumentTable - { - get - { - if (runningDocumentTable == null) - { - runningDocumentTable = GetService(GlobalServiceProvider); - } - - return runningDocumentTable; - } - } - - private void InitializeRunningDocumentTable() - { - if (RunningDocumentTable != null) - { - RunningDocumentTable.AdviseRunningDocTableEvents(this, out runningDocumentTableCookie); - } - } - - void IDisposable.Dispose() - { - if (runningDocumentTableCookie != 0) - { - runningDocumentTable.UnadviseRunningDocTableEvents(runningDocumentTableCookie); - runningDocumentTableCookie = 0; - } - } - #endregion - - #region Event Handlers - Editor / IVsRunningDocTableEvents Events - - // Populate the treeview with the contents of the SyntaxTree corresponding to the code document - // that is currently active in the editor. - private void RefreshSyntaxVisualizer() - { - if (IsVisible && activeWpfTextView != null) - { - var snapshot = activeWpfTextView.TextBuffer.CurrentSnapshot; - var contentType = snapshot.ContentType; - - if (contentType.IsOfType(VisualBasicContentType) || - contentType.IsOfType(CSharpContentType)) - { - // Get the Document corresponding to the currently active text snapshot. - var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); - if (document != null) - { - // Get the SyntaxTree and SemanticModel corresponding to the Document. - activeSyntaxTree = document.GetSyntaxTreeAsync().Result; - var activeSemanticModel = document.GetSemanticModelAsync().Result; - - // Display the SyntaxTree. - if (contentType.IsOfType(VisualBasicContentType) || contentType.IsOfType(CSharpContentType)) - { - syntaxVisualizer.DisplaySyntaxTree(activeSyntaxTree, activeSemanticModel); - } - - NavigateFromSource(); - } - } - } - } - - // When user clicks / selects text in the editor select the corresponding item in the treeview. - private void NavigateFromSource() - { - if (IsVisible && activeWpfTextView != null) - { - var span = activeWpfTextView.Selection.StreamSelectionSpan.SnapshotSpan.Span; - syntaxVisualizer.NavigateToBestMatch(span.Start, span.Length); - } - } - - // When user clicks on a particular item in the treeview select the corresponding text in the editor. - private void NavigateToSource(TextSpan span) - { - if (IsVisible && activeWpfTextView != null) - { - var snapShotSpan = span.ToSnapshotSpan(activeWpfTextView.TextBuffer.CurrentSnapshot); - - // See SyntaxVisualizerToolWindow_GotFocus and SyntaxVisualizerToolWindow_LostFocus - // for some notes about selection opacity and why it needs to be manipulated. - activeWpfTextView.Selection.Select(snapShotSpan, false); - activeWpfTextView.ViewScroller.EnsureSpanVisible(snapShotSpan); - } - } - - private void HandleSelectionChanged(object sender, EventArgs e) - { - NavigateFromSource(); - } - - // Below timer logic ensures that we won't refresh the treeview for each and every - // character that the user is typing. Instead we wait for a pause in the user's typing - // that is at least a few hundred milliseconds long before we refresh the treeview. - - private void HandleTextBufferChanged(object sender, EventArgs e) - { - if (typingTimer == null) - { - typingTimer = new DispatcherTimer - { - Interval = typingTimerTimeout - }; - typingTimer.Tick += HandleTypingTimerTimeout; - } - - typingTimer.Stop(); - typingTimer.Start(); - } - - private void HandleTextViewLostFocus(object sender, EventArgs e) - { - if (typingTimer != null) - { - typingTimer.Stop(); - } - } - - private void HandleTypingTimerTimeout(object sender, EventArgs e) - { - typingTimer.Stop(); - RefreshSyntaxVisualizer(); - } - - // Handle the case where the user opens a new code document / switches to a different code document. - int IVsRunningDocTableEvents.OnBeforeDocumentWindowShow(uint docCookie, int isFirstShow, IVsWindowFrame vsWindowFrame) - { - if (IsVisible && isFirstShow == 0) - { - var wpfTextView = vsWindowFrame.ToWpfTextView(); - if (wpfTextView != null) - { - var contentType = wpfTextView.TextBuffer.ContentType; - if (contentType.IsOfType(VisualBasicContentType) || - contentType.IsOfType(CSharpContentType)) - { - Clear(); - activeWpfTextView = wpfTextView; - activeWpfTextView.Selection.SelectionChanged += HandleSelectionChanged; - activeWpfTextView.TextBuffer.Changed += HandleTextBufferChanged; - activeWpfTextView.LostAggregateFocus += HandleTextViewLostFocus; - RefreshSyntaxVisualizer(); - } - } - } - - return VSConstants.S_OK; - } - - // Handle the case where the user closes the current code document / switches to a different code document. - int IVsRunningDocTableEvents.OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame vsWindowFrame) - { - if (IsVisible && activeWpfTextView != null) - { - var wpfTextView = vsWindowFrame.ToWpfTextView(); - if (wpfTextView == activeWpfTextView) - { - Clear(); - } - } - - return VSConstants.S_OK; - } - - #region Unused IVsRunningDocTableEvents Events - int IVsRunningDocTableEvents.OnBeforeLastDocumentUnlock(uint docCookie, uint lockType, uint readLocksRemaining, uint editLocksRemaining) - { - return VSConstants.S_OK; - } - - int IVsRunningDocTableEvents.OnAfterAttributeChange(uint docCookie, uint grfAttribs) - { - return VSConstants.S_OK; - } - - int IVsRunningDocTableEvents.OnAfterFirstDocumentLock(uint docCookie, uint lockType, uint readLocksRemaining, uint editLocksRemaining) - { - return VSConstants.S_OK; - } - - int IVsRunningDocTableEvents.OnAfterSave(uint docCookie) - { - return VSConstants.S_OK; - } - #endregion - #endregion - - #region Event Handlers - Directed Syntax Graph / IVsSolutionEvents Events - private string dgmlFilePath; - private IVsFileChangeEx fileChangeService; - private IVsFileChangeEx FileChangeService - { - get - { - if (fileChangeService == null) - { - fileChangeService = GetService(); - } - - return fileChangeService; - } - } - - private IVsSolution solutionService; - private IVsSolution SolutionService - { - get - { - if (solutionService == null) - { - solutionService = GetService(); - } - - return solutionService; - } - } - - private void DisplayDgml(XElement dgml) - { - uint cookie; - const int TRUE = -1; - - if (string.IsNullOrWhiteSpace(dgmlFilePath)) - { - var folderPath = Path.Combine(Path.GetTempPath(), - "Syntax-" + System.Diagnostics.Process.GetCurrentProcess().Id.ToString()); - Directory.CreateDirectory(folderPath); - dgmlFilePath = Path.Combine(folderPath, "Syntax.dgml"); - } - - // Check whether the file is already open in the 'design' view. - // If the file is already open in the desired view then we will update the - // contents of the file on disk with the new directed syntax graph and load - // this new graph into the already open view of the file. - if (VsShellUtilities.IsDocumentOpen( - ServiceProvider.GlobalProvider, dgmlFilePath, GuidList.GuidVsDesignerViewKind, - out var docUIHierarchy, out var docItemId, out var docWindowFrame) && docWindowFrame != null) - { - if (RunningDocumentTable.FindAndLockDocument((uint)_VSRDTFLAGS.RDT_NoLock, dgmlFilePath, - out var docHierarchy, out docItemId, - out var docDataIUnknownPointer, - out cookie) == VSConstants.S_OK) - { - var persistDocDataServiceGuid = typeof(IVsPersistDocData).GUID; - - if (Marshal.QueryInterface(docDataIUnknownPointer, ref persistDocDataServiceGuid, - out var persistDocDataServicePointer) == 0) - { - try - { - var persistDocDataService = - (IVsPersistDocData)Marshal.GetObjectForIUnknown(persistDocDataServicePointer); - - if (persistDocDataService != null) - { - // The below call ensures that there are no pop-ups from Visual Studio - // prompting the user to reload the file each time it is changed. - FileChangeService.IgnoreFile(0, dgmlFilePath, TRUE); - - // Update the file on disk with the new directed syntax graph. - dgml.Save(dgmlFilePath); - - // The below calls ensure that the file is refreshed inside Visual Studio - // so that the latest contents are displayed to the user. - FileChangeService.SyncFile(dgmlFilePath); - persistDocDataService.ReloadDocData((uint)_VSRELOADDOCDATA.RDD_IgnoreNextFileChange); - - // Make sure the directed syntax graph window is visible but don't give it focus. - docWindowFrame.ShowNoActivate(); - } - } - finally - { - Marshal.Release(persistDocDataServicePointer); - } - } - } - } - else - { - // Update the file on disk with the new directed syntax graph. - dgml.Save(dgmlFilePath); - - // Open the new directed syntax graph in the 'design' view. - VsShellUtilities.OpenDocument( - ServiceProvider.GlobalProvider, dgmlFilePath, GuidList.GuidVsDesignerViewKind, - out docUIHierarchy, out docItemId, out docWindowFrame); - - // Register event handler to ensure that directed syntax graph file is deleted when the solution is closed. - // This ensures that the file won't be persisted in the .suo file and that it therefore won't get re-opened - // when the solution is re-opened. - SolutionService.AdviseSolutionEvents(this, out cookie); - } - } - - private void DisplaySyntaxNodeDgml(SyntaxNode node) - { - if (activeWpfTextView != null) - { - var snapshot = activeWpfTextView.TextBuffer.CurrentSnapshot; - var contentType = snapshot.ContentType; - XElement dgml = null; - - if (contentType.IsOfType(CSharpContentType) || contentType.IsOfType(VisualBasicContentType)) - { - dgml = node.ToDgml(); - } - - DisplayDgml(dgml); - } - } - - private void DisplaySyntaxTokenDgml(SyntaxToken token) - { - if (activeWpfTextView != null) - { - var snapshot = activeWpfTextView.TextBuffer.CurrentSnapshot; - var contentType = snapshot.ContentType; - XElement dgml = null; - - if (contentType.IsOfType(CSharpContentType) || contentType.IsOfType(VisualBasicContentType)) - { - dgml = token.ToDgml(); - } - - DisplayDgml(dgml); - } - } - - private void DisplaySyntaxTriviaDgml(SyntaxTrivia trivia) - { - if (activeWpfTextView != null) - { - var snapshot = activeWpfTextView.TextBuffer.CurrentSnapshot; - var contentType = snapshot.ContentType; - XElement dgml = null; - - if (contentType.IsOfType(CSharpContentType) || contentType.IsOfType(VisualBasicContentType)) - { - dgml = trivia.ToDgml(); - } - - DisplayDgml(dgml); - } - } - - // Ensure that directed syntax graph file is deleted when the solution is closed. This ensures that - // the file won't be persisted in the .suo file and that it therefore won't get re-opened when the - // solution is re-opened. - int IVsSolutionEvents.OnBeforeCloseSolution(object ignore) - { - if (!string.IsNullOrWhiteSpace(dgmlFilePath)) - { - var folderPath = Path.GetDirectoryName(dgmlFilePath); - if (Directory.Exists(folderPath)) - { - Directory.Delete(folderPath, recursive: true); - } - } - - return VSConstants.S_OK; - } - - #region Unused IVsSolutionEvents Events - int IVsSolutionEvents.OnAfterCloseSolution(object ignore) - { - return VSConstants.S_OK; - } - - int IVsSolutionEvents.OnAfterLoadProject(IVsHierarchy ignore, IVsHierarchy ignore1) - { - return VSConstants.S_OK; - } - - int IVsSolutionEvents.OnAfterOpenProject(IVsHierarchy ignore, int ignore1) - { - return VSConstants.S_OK; - } - - int IVsSolutionEvents.OnAfterOpenSolution(object ignore, int ignore1) - { - return VSConstants.S_OK; - } - - int IVsSolutionEvents.OnBeforeCloseProject(IVsHierarchy ignore, int ignore1) - { - return VSConstants.S_OK; - } - - int IVsSolutionEvents.OnBeforeUnloadProject(IVsHierarchy ignore, IVsHierarchy ignore1) - { - return VSConstants.S_OK; - } - - int IVsSolutionEvents.OnQueryCloseProject(IVsHierarchy ignore, int ignore1, ref int ignore2) - { - return VSConstants.S_OK; - } - - int IVsSolutionEvents.OnQueryCloseSolution(object ignore, ref int ignore2) - { - return VSConstants.S_OK; - } - - int IVsSolutionEvents.OnQueryUnloadProject(IVsHierarchy ignore, ref int ignore2) - { - return VSConstants.S_OK; - } - #endregion - #endregion - - #region Event Handlers - Other - private void SyntaxVisualizerToolWindow_Loaded(object sender, RoutedEventArgs e) - { - HandleTextBufferChanged(sender, e); - } - - private void SyntaxVisualizerToolWindow_Unloaded(object sender, RoutedEventArgs e) - { - Clear(); - } - - private void SyntaxVisualizerToolWindow_GotFocus(object sender, RoutedEventArgs e) - { - if (activeWpfTextView != null && !activeWpfTextView.Properties.ContainsProperty("BackupOpacity")) - { - var selectionLayer = activeWpfTextView.GetAdornmentLayer(PredefinedAdornmentLayers.Selection); - - // Backup current selection opacity value. - activeWpfTextView.Properties.AddProperty("BackupOpacity", selectionLayer.Opacity); - - // Set selection opacity to a high value. This ensures that the text selection is visible - // even when the code editor loses focus (i.e. when user is changing the text selection by - // clicking on nodes in the TreeView). - selectionLayer.Opacity = 1; - } - } - - private void SyntaxVisualizerToolWindow_LostFocus(object sender, RoutedEventArgs e) - { - if (activeWpfTextView != null && activeWpfTextView.Properties.ContainsProperty("BackupOpacity")) - { - var selectionLayer = activeWpfTextView.GetAdornmentLayer(PredefinedAdornmentLayers.Selection); - - // Restore backed up selection opacity value. - selectionLayer.Opacity = (double)activeWpfTextView.Properties.GetProperty("BackupOpacity"); - activeWpfTextView.Properties.RemoveProperty("BackupOpacity"); - } - } - #endregion - } -} diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/SyntaxVisualizerExtension.csproj b/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/SyntaxVisualizerExtension.csproj deleted file mode 100644 index 9b2e3b4de..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/SyntaxVisualizerExtension.csproj +++ /dev/null @@ -1,97 +0,0 @@ - - - net461 - Roslyn.SyntaxVisualizer.Extension - Roslyn.SyntaxVisualizer.Extension - false - false - - - false - CommonExtensions - Microsoft\SyntaxVisualizer - Roslyn.SyntaxVisualizer.Extension - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SyntaxVisualizerContainer.xaml - - - MSBuild:Compile - Designer - - - Always - - - Always - - - - true - VSPackage - Designer - - - Menus.ctmenu - Designer - - - - - - - - - - - \ No newline at end of file diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/SyntaxVisualizerToolWindow.cs b/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/SyntaxVisualizerToolWindow.cs deleted file mode 100644 index d377cc00b..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/SyntaxVisualizerToolWindow.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using Microsoft.VisualStudio.Shell; - -namespace Roslyn.SyntaxVisualizer.Extension -{ - /// - /// This class implements the tool window exposed by this package and hosts a user control. - /// - /// In Visual Studio tool windows are composed of a frame (implemented by the shell) and a pane, - /// usually implemented by the package implementer. - /// - /// This class derives from the ToolWindowPane class provided from the MPF in order to use its - /// implementation of the IVsUIElementPane interface. - /// - [Guid("da7e21aa-da94-452d-8aa1-d1b23f73f576")] - public class SyntaxVisualizerToolWindow : ToolWindowPane - { - /// - /// Standard constructor for the tool window. - /// - public SyntaxVisualizerToolWindow() : - base(null) - { - // Set the window title reading it from the resources. - Caption = Resources.ToolWindowTitle; - - // Set the image that will appear on the tab of the window frame - // when docked with an other window - // The resource ID correspond to the one defined in the resx file - // while the Index is the offset in the bitmap strip. Each image in - // the strip being 16x16. - BitmapResourceID = 500; - BitmapIndex = 0; - - // This is the user control hosted by the tool window; Note that, even if this class implements IDisposable, - // we are not calling Dispose on this object. This is because ToolWindowPane calls Dispose on - // the object returned by the Content property. - Content = new SyntaxVisualizerContainer(this); - } - - internal TServiceInterface GetVsService() - where TServiceInterface : class - where TService : class - { - return (TServiceInterface)GetService(typeof(TService)); - } - } -} diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/source.extension.vsixmanifest b/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/source.extension.vsixmanifest deleted file mode 100644 index aa6fb8ac5..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/source.extension.vsixmanifest +++ /dev/null @@ -1,25 +0,0 @@ - - - - - .NET Compiler Platform Syntax Visualizer - The Syntax Visualizer is a Visual Studio extension that allows you to inspect and explore the syntax trees you'll use as you build applications and VS extensions atop the .NET Compiler Platform ("Roslyn"). - http://go.microsoft.com/fwlink/?LinkId=394587 - Eula.rtf - SyntaxTree.ico - roslyn,syntax,visualizer - - - - - - - - - - - - - - - diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.cs.xlf b/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.cs.xlf deleted file mode 100644 index 9ff6c3a2e..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.cs.xlf +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Can not create tool window. - Can not create tool window. - - - - Syntax Visualizer - Syntax Visualizer - - - - - \ No newline at end of file diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.de.xlf b/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.de.xlf deleted file mode 100644 index eb8618320..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.de.xlf +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Can not create tool window. - Can not create tool window. - - - - Syntax Visualizer - Syntax Visualizer - - - - - \ No newline at end of file diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.es.xlf b/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.es.xlf deleted file mode 100644 index 0d57089cc..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.es.xlf +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Can not create tool window. - Can not create tool window. - - - - Syntax Visualizer - Syntax Visualizer - - - - - \ No newline at end of file diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.fr.xlf b/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.fr.xlf deleted file mode 100644 index 2cc51e88f..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.fr.xlf +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Can not create tool window. - Can not create tool window. - - - - Syntax Visualizer - Syntax Visualizer - - - - - \ No newline at end of file diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.it.xlf b/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.it.xlf deleted file mode 100644 index ff1921ada..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.it.xlf +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Can not create tool window. - Can not create tool window. - - - - Syntax Visualizer - Syntax Visualizer - - - - - \ No newline at end of file diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.ja.xlf b/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.ja.xlf deleted file mode 100644 index ba36a0fab..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.ja.xlf +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Can not create tool window. - Can not create tool window. - - - - Syntax Visualizer - Syntax Visualizer - - - - - \ No newline at end of file diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.ko.xlf b/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.ko.xlf deleted file mode 100644 index d7057fa17..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.ko.xlf +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Can not create tool window. - Can not create tool window. - - - - Syntax Visualizer - Syntax Visualizer - - - - - \ No newline at end of file diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.pl.xlf b/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.pl.xlf deleted file mode 100644 index 67998cc27..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.pl.xlf +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Can not create tool window. - Can not create tool window. - - - - Syntax Visualizer - Syntax Visualizer - - - - - \ No newline at end of file diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.pt-BR.xlf b/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.pt-BR.xlf deleted file mode 100644 index 9bff41727..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.pt-BR.xlf +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Can not create tool window. - Can not create tool window. - - - - Syntax Visualizer - Syntax Visualizer - - - - - \ No newline at end of file diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.ru.xlf b/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.ru.xlf deleted file mode 100644 index 621aada0c..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.ru.xlf +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Can not create tool window. - Can not create tool window. - - - - Syntax Visualizer - Syntax Visualizer - - - - - \ No newline at end of file diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.tr.xlf b/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.tr.xlf deleted file mode 100644 index 20409ebf0..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.tr.xlf +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Can not create tool window. - Can not create tool window. - - - - Syntax Visualizer - Syntax Visualizer - - - - - \ No newline at end of file diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.zh-Hans.xlf b/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.zh-Hans.xlf deleted file mode 100644 index 685878f7b..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.zh-Hans.xlf +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Can not create tool window. - Can not create tool window. - - - - Syntax Visualizer - Syntax Visualizer - - - - - \ No newline at end of file diff --git a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.zh-Hant.xlf b/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.zh-Hant.xlf deleted file mode 100644 index a9bfed4c3..000000000 --- a/src/Tools/SyntaxVisualizer/SyntaxVisualizerExtension/xlf/Resources.zh-Hant.xlf +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - Can not create tool window. - Can not create tool window. - - - - Syntax Visualizer - Syntax Visualizer - - - - - \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/AssemblyVersionGenerator/AssemblyVersionGenerator.cs b/src/VisualStudio.Roslyn.SDK/AssemblyVersionGenerator/AssemblyVersionGenerator.cs new file mode 100644 index 000000000..84d961638 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/AssemblyVersionGenerator/AssemblyVersionGenerator.cs @@ -0,0 +1,24 @@ +using System; +using System.Diagnostics; +using Microsoft.CodeAnalysis; + +namespace AssemblyVersionGenerator +{ + [Generator] + public class AssemblyVersionGenerator : ISourceGenerator + { + public void Execute(GeneratorExecutionContext context) + { + context.AddSource("assemblyversion.g.cs", $@" + +internal class AssemblyVersion +{{ + public const string Version = ""{context.Compilation.Assembly.Identity.Version}""; +}}"); + } + + public void Initialize(GeneratorInitializationContext context) + { + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/AssemblyVersionGenerator/AssemblyVersionGenerator.csproj b/src/VisualStudio.Roslyn.SDK/AssemblyVersionGenerator/AssemblyVersionGenerator.csproj new file mode 100644 index 000000000..cffbb806f --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/AssemblyVersionGenerator/AssemblyVersionGenerator.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + enable + true + + + + + + + diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/CommandLineArgumentsDataSource.cs b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/CommandLineArgumentsDataSource.cs new file mode 100644 index 000000000..22d274bfd --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/CommandLineArgumentsDataSource.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.ComponentModel.Composition; +using System.Threading; +using System.Threading.Tasks; +using System.Threading.Tasks.Dataflow; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.ProjectSystem.VS; + +namespace Roslyn.ComponentDebugger +{ + [Export] + [AppliesTo("(" + ProjectCapabilities.CSharp + " | " + ProjectCapabilities.VB + ") & !" + ProjectCapabilities.SharedAssetsProject)] + public class CommandLineArgumentsDataSource : UnconfiguredProjectHostBridge, IProjectVersionedValue>, IProjectVersionedValue>> + { + private readonly IActiveConfiguredProjectSubscriptionService _activeProjectSubscriptionService; + + [ImportingConstructor] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "MEF ensures not null")] + public CommandLineArgumentsDataSource(IProjectThreadingService projectThreadingService, IActiveConfiguredProjectSubscriptionService activeProjectSubscriptionService) + : base(projectThreadingService.JoinableTaskContext) + { + _activeProjectSubscriptionService = activeProjectSubscriptionService; + } + + public async Task> GetArgsAsync() + { + using (JoinableCollection.Join()) + { + await this.InitializeAsync().ConfigureAwait(true); + return this.AppliedValue?.Value ?? ImmutableArray.Empty; + } + } + + protected override bool BlockInitializeOnFirstAppliedValue => true; + + protected override Task InitializeInnerCoreAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + protected override IDisposable LinkExternalInput(ITargetBlock> targetBlock) + { + JoinUpstreamDataSources(_activeProjectSubscriptionService.ProjectBuildRuleSource); + return _activeProjectSubscriptionService.ProjectBuildRuleSource.SourceBlock.LinkTo(target: targetBlock, + linkOptions: new DataflowLinkOptions { PropagateCompletion = true }, + initialDataAsNew: true, + suppressVersionOnlyUpdates: true, + ruleNames: Constants.CommandLineArgsRuleName); + } + + protected override Task>?> PreprocessAsync(IProjectVersionedValue? input, IProjectVersionedValue>? previousOutput) + { + if (input is null) + { + throw new ArgumentNullException(nameof(input)); + } + + var description = input.Value.ProjectChanges[Constants.CommandLineArgsRuleName]; + return Task.FromResult>?>(new ProjectVersionedValue>(description.After.Items.Keys.ToImmutableArray(), input.DataSourceVersions)); + } + + protected override Task ApplyAsync(IProjectVersionedValue> value) + { + AppliedValue = value; + return Task.CompletedTask; + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/ComponentDebuggerLaunchProfile.xaml b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/ComponentDebuggerLaunchProfile.xaml new file mode 100644 index 000000000..656b12969 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/ComponentDebuggerLaunchProfile.xaml @@ -0,0 +1,43 @@ + + + + + DebugRoslynComponent + + + AE27A6B0-E345-4288-96DF-5EAF394EE369 + + + 3644 + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/Constants.cs b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/Constants.cs new file mode 100644 index 000000000..3c8bb093d --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/Constants.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Roslyn.ComponentDebugger +{ + internal static class Constants + { + public const string RoslynComponentCapability = "RoslynComponent"; + + public const string CommandName = "DebugRoslynComponent"; + + public const string TargetProjectKeyName = "targetProject"; + + public const string CommandLineArgsRuleName = "CompilerCommandLineArgs"; + } +} diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/DebugProfileProvider.cs b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/DebugProfileProvider.cs new file mode 100644 index 000000000..5f2ca2b3e --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/DebugProfileProvider.cs @@ -0,0 +1,97 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.ProjectSystem.Debug; +using Microsoft.VisualStudio.ProjectSystem.VS.Debug; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Threading; +using Task = System.Threading.Tasks.Task; + +namespace Roslyn.ComponentDebugger +{ + [Export(typeof(IDebugProfileLaunchTargetsProvider))] + [AppliesTo(Constants.RoslynComponentCapability)] + public class DebugProfileProvider : IDebugProfileLaunchTargetsProvider + { + private readonly ConfiguredProject _configuredProject; + private readonly LaunchSettingsManager _launchSettingsManager; + private readonly IProjectThreadingService _threadingService; + private readonly AsyncLazy _compilerRoot; + + [ImportingConstructor] + [Obsolete("This exported object must be obtained through the MEF export provider.", error: true)] + public DebugProfileProvider(ConfiguredProject configuredProject, LaunchSettingsManager launchSettingsManager, SVsServiceProvider? serviceProvider, IProjectThreadingService threadingService) + { + _configuredProject = configuredProject; + _launchSettingsManager = launchSettingsManager; + _threadingService = threadingService; + + _compilerRoot = new AsyncLazy(() => GetCompilerRootAsync(serviceProvider), _threadingService.JoinableTaskFactory); + } + + public Task OnAfterLaunchAsync(DebugLaunchOptions launchOptions, ILaunchProfile profile) => Task.CompletedTask; + + public Task OnBeforeLaunchAsync(DebugLaunchOptions launchOptions, ILaunchProfile profile) => Task.CompletedTask; + + public bool SupportsProfile(ILaunchProfile? profile) => Constants.CommandName.Equals(profile?.CommandName, StringComparison.Ordinal); + + public async Task> QueryDebugTargetsAsync(DebugLaunchOptions launchOptions, ILaunchProfile? profile) + { + // set up the managed (net fx) debugger to start a process + // https://github.com/dotnet/roslyn-sdk/issues/729 + var settings = new DebugLaunchSettings(launchOptions) + { + LaunchDebugEngineGuid = Microsoft.VisualStudio.ProjectSystem.Debug.DebuggerEngines.ManagedOnlyEngine, + LaunchOperation = DebugLaunchOperation.CreateProcess + }; + + var compilerRoot = await _compilerRoot.GetValueAsync().ConfigureAwait(true); + if (compilerRoot is object) + { + // try and get the target project + var targetProjectUnconfigured = await _launchSettingsManager.TryGetProjectForLaunchAsync(profile).ConfigureAwait(true); + if (targetProjectUnconfigured is object) + { + settings.CurrentDirectory = Path.GetDirectoryName(targetProjectUnconfigured.FullPath); + var compiler = _configuredProject.Capabilities.Contains(ProjectCapabilities.VB) ? "vbc.exe" : "csc.exe"; + settings.Executable = Path.Combine(compilerRoot, compiler); + + // get its compilation args + var args = await targetProjectUnconfigured.GetCompilationArgumentsAsync().ConfigureAwait(true); + + // append the command line args to the debugger launch + settings.Arguments = string.Join(" ", args); + } + } + // https://github.com/dotnet/roslyn-sdk/issues/728 : better error handling + return new IDebugLaunchSettings[] { settings }; + } + + private async Task GetCompilerRootAsync(SVsServiceProvider? serviceProvider) + { + await _threadingService.SwitchToUIThread(); + + // https://github.com/dotnet/roslyn-sdk/issues/729 : don't hardcode net fx compiler + var shell = (IVsShell?)serviceProvider?.GetService(typeof(SVsShell)); + if (shell is object + && shell.GetProperty((int)__VSSPROPID2.VSSPROPID_InstallRootDir, out var rootDirObj) == VSConstants.S_OK + && rootDirObj is string rootDir) + { + return Path.Combine(rootDir, "MSBuild", "Current", "Bin", "Roslyn"); + } + + return null; + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/LaunchSettingsManager.cs b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/LaunchSettingsManager.cs new file mode 100644 index 000000000..44a5d49a2 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/LaunchSettingsManager.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.ProjectSystem.Debug; + +namespace Roslyn.ComponentDebugger +{ + [Export] + public class LaunchSettingsManager + { + private readonly UnconfiguredProject _owningProject; + private readonly IDebugTokenReplacer _tokenReplacer; + + [ImportingConstructor] + public LaunchSettingsManager(UnconfiguredProject owningProject, IDebugTokenReplacer tokenReplacer) + { + _owningProject = owningProject; + _tokenReplacer = tokenReplacer; + } + + public async Task TryGetProjectForLaunchAsync(ILaunchProfile? profile) + { + UnconfiguredProject? targetProject = null; + object? value = null; + profile?.OtherSettings?.TryGetValue(Constants.TargetProjectKeyName, out value); + + if (value is string targetProjectPath) + { + // expand any variables in the path, and root it based on this project + var replacedProjectPath = await _tokenReplacer.ReplaceTokensInStringAsync(targetProjectPath, true).ConfigureAwait(true); + replacedProjectPath = _owningProject.MakeRooted(replacedProjectPath); + + targetProject = ((IProjectService2)_owningProject.Services.ProjectService).GetLoadedProject(replacedProjectPath); + } + return targetProject; + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/ProjectUtilities.cs b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/ProjectUtilities.cs new file mode 100644 index 000000000..4c2a4a769 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/ProjectUtilities.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.Utilities; + +namespace Roslyn.ComponentDebugger +{ + public static class ProjectUtilities + { + public static async Task> GetComponentReferencingProjectsAsync(this UnconfiguredProject unconfiguredProject) + { + var targetProjects = ArrayBuilder.GetInstance(); + + // get the output assembly for this project + var projectArgs = await unconfiguredProject.GetCompilationArgumentsAsync().ConfigureAwait(false); + var targetArg = projectArgs.LastOrDefault(a => a.StartsWith("/out:", StringComparison.OrdinalIgnoreCase)); + var target = Path.GetFileName(targetArg); + + var projectService = unconfiguredProject.Services.ProjectService; + foreach (var targetProjectUnconfigured in projectService.LoadedUnconfiguredProjects) + { + // check if the args contain the project as an analyzer ref + foreach (var arg in await targetProjectUnconfigured.GetCompilationArgumentsAsync().ConfigureAwait(false)) + { + if (arg.StartsWith("/analyzer:", StringComparison.OrdinalIgnoreCase) + && arg.EndsWith(target, StringComparison.OrdinalIgnoreCase)) + { + targetProjects.Add(targetProjectUnconfigured); + } + } + } + return targetProjects.ToImmutableAndFree(); + } + + public static Task> GetCompilationArgumentsAsync(this UnconfiguredProject project) + { + if (project is null) + { + throw new ArgumentNullException(nameof(project)); + } + + var dataSource = project.Services.ExportProvider.GetExportedValueOrDefault(); + if (dataSource is object) + { + return dataSource.GetArgsAsync(); + } + + return Task.FromResult(ImmutableArray.Empty); + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/Roslyn.ComponentDebugger.csproj b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/Roslyn.ComponentDebugger.csproj new file mode 100644 index 000000000..28f17264f --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/Roslyn.ComponentDebugger.csproj @@ -0,0 +1,35 @@ + + + + + Library + Roslyn.ComponentDebugger + net472 + enable + true + $(NoWarn);NU1603;NU1605 + + + + + Never + MSBuild:GenerateRuleSourceFromXaml + Designer + None + None + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/RuleExporter.cs b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/RuleExporter.cs new file mode 100644 index 000000000..38382da9f --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/RuleExporter.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.ProjectSystem.Properties; + +namespace Roslyn.ComponentDebugger +{ + internal static class RuleExporter + { + /// + /// Used to export the XAML rule via MEF + /// + [ExportPropertyXamlRuleDefinition( + xamlResourceAssemblyName: "Roslyn.ComponentDebugger, Version=" + AssemblyVersion.Version + ", Culture=neutral, PublicKeyToken=31bf3856ad364e35", + xamlResourceStreamName: "XamlRuleToCode:ComponentDebuggerLaunchProfile.xaml", + context: PropertyPageContexts.Project)] + [AppliesTo(Constants.RoslynComponentCapability)] +#pragma warning disable CS0649 + public static int LaunchProfileRule; +#pragma warning restore CS0649 + } +} diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/TargetProjectEnumProvider.cs b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/TargetProjectEnumProvider.cs new file mode 100644 index 000000000..23582bd2a --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/TargetProjectEnumProvider.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Build.Framework.XamlTypes; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.ProjectSystem.Properties; + +namespace Roslyn.ComponentDebugger +{ + [ExportDynamicEnumValuesProvider(nameof(TargetProjectEnumProvider))] + [AppliesTo(Constants.RoslynComponentCapability)] + public class TargetProjectEnumProvider : IDynamicEnumValuesProvider + { + private readonly UnconfiguredProject _unconfiguredProject; + private readonly LaunchSettingsManager _launchSettingsManager; + + [ImportingConstructor] + public TargetProjectEnumProvider(UnconfiguredProject unconfiguredProject, LaunchSettingsManager launchSettingsManager) + { + _unconfiguredProject = unconfiguredProject; + _launchSettingsManager = launchSettingsManager; + } + + public async Task GetProviderAsync(IList? options) + { + // get the targets for this project + var projects = await _unconfiguredProject.GetComponentReferencingProjectsAsync().ConfigureAwait(false); + + // convert to display values of friendly name + relative path + var displayValues = projects.Select(p => (Path.GetFileNameWithoutExtension(p.FullPath), _unconfiguredProject.MakeRelative(p.FullPath))).ToImmutableArray(); + + return new TargetProjectEnumValuesGenerator(displayValues); + } + + private class TargetProjectEnumValuesGenerator : IDynamicEnumValuesGenerator + { + private readonly ImmutableArray<(string display, string path)> _referencingProjects; + + public bool AllowCustomValues => false; + + public TargetProjectEnumValuesGenerator(ImmutableArray<(string display, string path)> referencingProjects) + { + _referencingProjects = referencingProjects; + } + + public Task> GetListedValuesAsync() + { + var values = _referencingProjects.Select(p => new PageEnumValue(new EnumValue() { DisplayName = p.display, Name = p.path})).Cast().ToImmutableArray(); + return Task.FromResult>(values); + } + + /// + /// The user can't add arbitrary projects from the UI, so this is unsupported + /// + public Task TryCreateEnumValueAsync(string userSuppliedValue) => Task.FromResult(null); + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.cs.xlf b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.cs.xlf new file mode 100644 index 000000000..70107d3d7 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.cs.xlf @@ -0,0 +1,32 @@ + + + + + + A project that uses this component, whose compilation will be debugged. + A project that uses this component, whose compilation will be debugged. + + + + Target Project + Target Project + + + + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + + + + Roslyn Component + Roslyn Component + + + + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.de.xlf b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.de.xlf new file mode 100644 index 000000000..3f73ba68a --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.de.xlf @@ -0,0 +1,32 @@ + + + + + + A project that uses this component, whose compilation will be debugged. + A project that uses this component, whose compilation will be debugged. + + + + Target Project + Target Project + + + + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + + + + Roslyn Component + Roslyn Component + + + + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.es.xlf b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.es.xlf new file mode 100644 index 000000000..49696eefb --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.es.xlf @@ -0,0 +1,32 @@ + + + + + + A project that uses this component, whose compilation will be debugged. + A project that uses this component, whose compilation will be debugged. + + + + Target Project + Target Project + + + + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + + + + Roslyn Component + Roslyn Component + + + + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.fr.xlf b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.fr.xlf new file mode 100644 index 000000000..b0ad7e720 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.fr.xlf @@ -0,0 +1,32 @@ + + + + + + A project that uses this component, whose compilation will be debugged. + A project that uses this component, whose compilation will be debugged. + + + + Target Project + Target Project + + + + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + + + + Roslyn Component + Roslyn Component + + + + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.it.xlf b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.it.xlf new file mode 100644 index 000000000..6e9993b18 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.it.xlf @@ -0,0 +1,32 @@ + + + + + + A project that uses this component, whose compilation will be debugged. + A project that uses this component, whose compilation will be debugged. + + + + Target Project + Target Project + + + + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + + + + Roslyn Component + Roslyn Component + + + + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.ja.xlf b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.ja.xlf new file mode 100644 index 000000000..38b0fc455 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.ja.xlf @@ -0,0 +1,32 @@ + + + + + + A project that uses this component, whose compilation will be debugged. + A project that uses this component, whose compilation will be debugged. + + + + Target Project + Target Project + + + + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + + + + Roslyn Component + Roslyn Component + + + + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.ko.xlf b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.ko.xlf new file mode 100644 index 000000000..55caf820f --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.ko.xlf @@ -0,0 +1,32 @@ + + + + + + A project that uses this component, whose compilation will be debugged. + A project that uses this component, whose compilation will be debugged. + + + + Target Project + Target Project + + + + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + + + + Roslyn Component + Roslyn Component + + + + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.pl.xlf b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.pl.xlf new file mode 100644 index 000000000..d623a3953 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.pl.xlf @@ -0,0 +1,32 @@ + + + + + + A project that uses this component, whose compilation will be debugged. + A project that uses this component, whose compilation will be debugged. + + + + Target Project + Target Project + + + + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + + + + Roslyn Component + Roslyn Component + + + + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.pt-BR.xlf b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.pt-BR.xlf new file mode 100644 index 000000000..d93d99696 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.pt-BR.xlf @@ -0,0 +1,32 @@ + + + + + + A project that uses this component, whose compilation will be debugged. + A project that uses this component, whose compilation will be debugged. + + + + Target Project + Target Project + + + + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + + + + Roslyn Component + Roslyn Component + + + + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.ru.xlf b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.ru.xlf new file mode 100644 index 000000000..5890416c8 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.ru.xlf @@ -0,0 +1,32 @@ + + + + + + A project that uses this component, whose compilation will be debugged. + A project that uses this component, whose compilation will be debugged. + + + + Target Project + Target Project + + + + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + + + + Roslyn Component + Roslyn Component + + + + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.tr.xlf b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.tr.xlf new file mode 100644 index 000000000..8c8db3e8f --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.tr.xlf @@ -0,0 +1,32 @@ + + + + + + A project that uses this component, whose compilation will be debugged. + A project that uses this component, whose compilation will be debugged. + + + + Target Project + Target Project + + + + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + + + + Roslyn Component + Roslyn Component + + + + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.zh-Hans.xlf b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.zh-Hans.xlf new file mode 100644 index 000000000..1e3273ef3 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.zh-Hans.xlf @@ -0,0 +1,32 @@ + + + + + + A project that uses this component, whose compilation will be debugged. + A project that uses this component, whose compilation will be debugged. + + + + Target Project + Target Project + + + + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + + + + Roslyn Component + Roslyn Component + + + + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.zh-Hant.xlf b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.zh-Hant.xlf new file mode 100644 index 000000000..5720b1a0c --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/ComponentDebugger/xlf/ComponentDebuggerLaunchProfile.xaml.zh-Hant.xlf @@ -0,0 +1,32 @@ + + + + + + A project that uses this component, whose compilation will be debugged. + A project that uses this component, whose compilation will be debugged. + + + + Target Project + Target Project + + + + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + Allows a user to debug a Roslyn Component by running it in the context of another projects build. + + + + Roslyn Component + Roslyn Component + + + + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + A Roslyn Component can be debugged in the context of compiling a second project that uses it. Ensure your target project is referencing this component for it to appear in the list. + + + + + \ No newline at end of file diff --git a/src/Templates/EULA.rtf b/src/VisualStudio.Roslyn.SDK/EULA.rtf similarity index 100% rename from src/Templates/EULA.rtf rename to src/VisualStudio.Roslyn.SDK/EULA.rtf diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/Roslyn.SDK.Template.Wizard.csproj b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/Roslyn.SDK.Template.Wizard.csproj new file mode 100644 index 000000000..2c88df73e --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/Roslyn.SDK.Template.Wizard.csproj @@ -0,0 +1,24 @@ + + + + net472 + Roslyn.SDK.Template.Wizard + + + + + + + + + + + + + + + + + + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKAnalyzerTemplateWizard.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKAnalyzerTemplateWizard.cs new file mode 100644 index 000000000..b0a5791a5 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKAnalyzerTemplateWizard.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using EnvDTE; + +public class RoslynSDKAnalyzerTemplateWizard : RoslynSDKChildTemplateWizard +{ + public static Project? Project { get; private set; } + + public override void OnProjectFinishedGenerating(Project project) + { + Project = project; + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKChildTemplateWizard.InterfaceMembers.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKChildTemplateWizard.InterfaceMembers.cs new file mode 100644 index 000000000..8d6ba5d8f --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKChildTemplateWizard.InterfaceMembers.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using EnvDTE; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.TemplateWizard; + +public partial class RoslynSDKChildTemplateWizard : IWizard +{ + public void BeforeOpeningFile(ProjectItem projectItem) { } + public void ProjectFinishedGenerating(Project project) + { + OnProjectFinishedGenerating(project); + } + public void RunFinished() { } + public bool ShouldAddProjectItem(string filePath) => true; + public void ProjectItemFinishedGenerating(ProjectItem projectItem) { } + public void RunStarted(object automationObject, Dictionary replacementsDictionary, WizardRunKind runKind, object[] customParams) + { + ThreadHelper.ThrowIfNotOnUIThread(); + if (automationObject is DTE dte) + { + OnRunStarted(dte, replacementsDictionary, runKind, customParams); + } + } +} diff --git a/src/Tools/RoslynSDKTemplateWizard/RoslynSDKChildTemplateWizard.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKChildTemplateWizard.cs similarity index 81% rename from src/Tools/RoslynSDKTemplateWizard/RoslynSDKChildTemplateWizard.cs rename to src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKChildTemplateWizard.cs index c77801003..81fb6132e 100644 --- a/src/Tools/RoslynSDKTemplateWizard/RoslynSDKChildTemplateWizard.cs +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKChildTemplateWizard.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; using EnvDTE; using Microsoft.VisualStudio.TemplateWizard; diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKCodeFixTemplateWizard.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKCodeFixTemplateWizard.cs new file mode 100644 index 000000000..f19a27dea --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKCodeFixTemplateWizard.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using EnvDTE; +using Microsoft.VisualStudio.Shell; +using VSLangProj; + +public class RoslynSDKCodeFixTemplateWizard : RoslynSDKChildTemplateWizard +{ + public static Project? Project { get; private set; } + + public override void OnProjectFinishedGenerating(Project project) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + Project = project; + + // There is no good way for the test project to reference the main project, so we will use the wizard. + if (project.Object is VSProject vsProject) + { + _ = vsProject.References.AddProject(RoslynSDKAnalyzerTemplateWizard.Project); + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKPackageTemplateWizard.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKPackageTemplateWizard.cs new file mode 100644 index 000000000..8926645e5 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKPackageTemplateWizard.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using EnvDTE; +using Microsoft.VisualStudio.Shell; +using VSLangProj; + +public class RoslynSDKPackageTemplateWizard : RoslynSDKChildTemplateWizard +{ + public override void OnProjectFinishedGenerating(Project project) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + // There is no good way for the test project to reference the main project, so we will use the wizard. + if (project.Object is VSProject vsProject) + { + _ = vsProject.References.AddProject(RoslynSDKAnalyzerTemplateWizard.Project); + _ = vsProject.References.AddProject(RoslynSDKCodeFixTemplateWizard.Project); + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKRootTemplateWizard.InterfaceMembers.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKRootTemplateWizard.InterfaceMembers.cs new file mode 100644 index 000000000..d6559c2fd --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKRootTemplateWizard.InterfaceMembers.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using EnvDTE; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.TemplateWizard; + +public partial class RoslynSDKRootTemplateWizard : IWizard +{ + public void BeforeOpeningFile(ProjectItem projectItem) { } + public void ProjectFinishedGenerating(Project project) { } + public void RunFinished() { } + public bool ShouldAddProjectItem(string filePath) => true; + public void ProjectItemFinishedGenerating(ProjectItem projectItem) { } + public void RunStarted(object automationObject, Dictionary replacementsDictionary, WizardRunKind runKind, object[] customParams) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + if (automationObject is DTE dte) + { + OnRunStarted(dte, replacementsDictionary, runKind, customParams); + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKRootTemplateWizard.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKRootTemplateWizard.cs new file mode 100644 index 000000000..a74df4b69 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKRootTemplateWizard.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.IO; +using System.Text; +using EnvDTE; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.TemplateWizard; + +public partial class RoslynSDKRootTemplateWizard +{ + public static Dictionary GlobalDictionary = new Dictionary(); + + private void OnRunStarted(DTE dte, Dictionary replacementsDictionary, WizardRunKind runKind, object[] customParams) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + // add the root project name (the name the user passed in) to the global replacement dictionary + GlobalDictionary["$saferootprojectname$"] = replacementsDictionary["$safeprojectname$"]; + GlobalDictionary["$saferootidentifiername$"] = replacementsDictionary["$safeprojectname$"].Replace(".",""); + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKTestTemplateWizard.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKTestTemplateWizard.cs new file mode 100644 index 000000000..7113573da --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKTestTemplateWizard.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using EnvDTE; +using Microsoft.VisualStudio.Shell; +using VSLangProj; + +public class RoslynSDKTestTemplateWizard : RoslynSDKChildTemplateWizard +{ + public override void OnProjectFinishedGenerating(Project project) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + // There is no good way for the test project to reference the main project, so we will use the wizard. + if (project.Object is VSProject vsProject) + { + _ = vsProject.References.AddProject(RoslynSDKAnalyzerTemplateWizard.Project); + _ = vsProject.References.AddProject(RoslynSDKCodeFixTemplateWizard.Project); + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKVsixTemplateWizardSecondProject.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKVsixTemplateWizardSecondProject.cs new file mode 100644 index 000000000..ea0732b31 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKVsixTemplateWizardSecondProject.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using EnvDTE; +using Microsoft.VisualStudio.Shell; + +public class RoslynSDKVsixTemplateWizardSecondProject : RoslynSDKTestTemplateWizard +{ + public override void OnProjectFinishedGenerating(Project project) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + base.OnProjectFinishedGenerating(project); + + // set the VSIX project to be the starting project + var dte = project.DTE; + if (dte.Solution.Projects.Count == 2) + { + dte.Solution.Properties.Item("StartupProject").Value = project.Name; + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKVsixTemplateWizardThirdProject.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKVsixTemplateWizardThirdProject.cs new file mode 100644 index 000000000..9ea2bb31f --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKVsixTemplateWizardThirdProject.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using EnvDTE; +using Microsoft.VisualStudio.Shell; + +public class RoslynSDKVsixTemplateWizardThirdProject : RoslynSDKTestTemplateWizard +{ + public override void OnProjectFinishedGenerating(Project project) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + base.OnProjectFinishedGenerating(project); + + // set the VSIX project to be the starting project + var dte = project.DTE; + if (dte.Solution.Projects.Count == 3) + { + dte.Solution.Properties.Item("StartupProject").Value = project.Name; + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/CSRef.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/CSRef.vstemplate new file mode 100644 index 000000000..7a1b997dd --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/CSRef.vstemplate @@ -0,0 +1,43 @@ + + + + Code Refactoring (.NET Standard) + Create a C# refactoring, deployed as a VSIX extension + CodeInformation.ico + CSharp + 2.0 + 1000 + Microsoft.CSharp.CodeRefactoring + true + true + CodeRefactoring + true + true + C# + Windows + Linux + macOS + VSSDK + Roslyn + Extensions + + + + + + ProjectTemplates\CSharp\CodeRefactoring\Ref\CSharpCodeRefactoring.vstemplate + + + ProjectTemplates\CSharp\CodeRefactoring\Vsix\Deployment.vstemplate + + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKRootTemplateWizard + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/CSharpDiagnostic.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/CSharpDiagnostic.vstemplate new file mode 100644 index 000000000..9cbb893ee --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/CSharpDiagnostic.vstemplate @@ -0,0 +1,52 @@ + + + + Analyzer with Code Fix (.NET Standard) + Create a C# analyzer that comes with a code fix and generates diagnostics. The analyzer can be deployed as either a NuGet package or a VSIX extension. + CodeInformation.ico + CSharp + 2.0 + 1000 + Microsoft.CSharp.Analyzer + true + true + Analyzer + true + true + C# + Windows + Linux + macOS + VSSDK + Roslyn + Extensions + + + + + + ProjectTemplates\CSharp\Diagnostic\Analyzer\DiagnosticAnalyzer.vstemplate + + + ProjectTemplates\CSharp\Diagnostic\CodeFix\CodeFixProvider.vstemplate + + + ProjectTemplates\CSharp\Diagnostic\Package\Package.vstemplate + + + ProjectTemplates\CSharp\Diagnostic\Test\Test.vstemplate + + + ProjectTemplates\CSharp\Diagnostic\Vsix\Deployment.vstemplate + + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKRootTemplateWizard + + \ No newline at end of file diff --git a/src/Templates/VS2015/CodeInformation.ico b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/CodeInformation.ico similarity index 100% rename from src/Templates/VS2015/CodeInformation.ico rename to src/VisualStudio.Roslyn.SDK/Roslyn.SDK/CodeInformation.ico diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/Directory.Build.props b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/Directory.Build.props new file mode 100644 index 000000000..8f1108cc1 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/Directory.Build.props @@ -0,0 +1,13 @@ + + + + Templates\$(MSBuildProjectName) + + + + false + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/CSharp/Analyzer/Analyzer.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/CSharp/Analyzer/Analyzer.cs new file mode 100644 index 000000000..abbe4d7ed --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/CSharp/Analyzer/Analyzer.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace $rootnamespace$ +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class $safeitemname$ : DiagnosticAnalyzer + { + public const string DiagnosticId = "$safeitemname$"; + internal static readonly LocalizableString Title = "$safeitemname$ Title"; + internal static readonly LocalizableString MessageFormat = "$safeitemname$ '{0}'"; + internal const string Category = "$safeitemname$ Category"; + + internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, true); + + public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } } + + public override void Initialize(AnalysisContext context) + { + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/CSharp/Analyzer/CSharpAnalyzer.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/CSharp/Analyzer/CSharpAnalyzer.vstemplate new file mode 100644 index 000000000..757e1b345 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/CSharp/Analyzer/CSharpAnalyzer.vstemplate @@ -0,0 +1,14 @@ + + + Microsoft.CodeAnalysis.CSharp.Analyzer + Analyzer.cs + Analyzer + Create a C# diagnostic analyzer. + CSharp + 10 + + + + Analyzer.cs + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/CSharp/CodeFix/CSharpCodeFix.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/CSharp/CodeFix/CSharpCodeFix.vstemplate new file mode 100644 index 000000000..b15ed3036 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/CSharp/CodeFix/CSharpCodeFix.vstemplate @@ -0,0 +1,14 @@ + + + Microsoft.CodeAnalysis.CSharp.CodeFix + CodeFix.cs + CodeFix + Create a C# code fix. + CSharp + 10 + + + + CodeFix.cs + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/CSharp/CodeFix/CodeFix.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/CSharp/CodeFix/CodeFix.cs new file mode 100644 index 000000000..36af2f56d --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/CSharp/CodeFix/CodeFix.cs @@ -0,0 +1,39 @@ +using System; +using System.Composition; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Rename; +using Microsoft.CodeAnalysis.Text; + +namespace $rootnamespace$ +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof($safeitemname$)), Shared] + public class $safeitemname$ : CodeFixProvider + { + // TODO: Replace with actual diagnostic id that should trigger this fix. + public const string DiagnosticId = "$safeitemname$"; + + public sealed override ImmutableArray FixableDiagnosticIds + { + get { return ImmutableArray.Create(DiagnosticId); } + } + + public sealed override FixAllProvider GetFixAllProvider() + { + return WellKnownFixAllProviders.BatchFixer; + } + + public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/CSharp/Refactoring/CSharpRefactoring.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/CSharp/Refactoring/CSharpRefactoring.vstemplate new file mode 100644 index 000000000..1fc5b3848 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/CSharp/Refactoring/CSharpRefactoring.vstemplate @@ -0,0 +1,14 @@ + + + Microsoft.CodeAnalysis.CSharp.Refactoring + Refactoring.cs + Refactoring + Create a C# code refactoring. + CSharp + 10 + + + + Refactoring.cs + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/CSharp/Refactoring/Refactoring.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/CSharp/Refactoring/Refactoring.cs new file mode 100644 index 000000000..e5b04f7e3 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/CSharp/Refactoring/Refactoring.cs @@ -0,0 +1,26 @@ +using System; +using System.Composition; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Rename; +using Microsoft.CodeAnalysis.Text; + +namespace $rootnamespace$ +{ + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof($safeitemname$)), Shared] + internal class $safeitemname$ : CodeRefactoringProvider + { + public sealed override Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/VisualBasic/Analyzer/Analyzer.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/VisualBasic/Analyzer/Analyzer.vb new file mode 100644 index 000000000..87ea8f188 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/VisualBasic/Analyzer/Analyzer.vb @@ -0,0 +1,28 @@ +Imports System.Collections.Immutable +Imports System.Threading +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + + +Public Class $safeitemname$ + Inherits DiagnosticAnalyzer + + Public Const DiagnosticId = "$safeitemname$" + Friend Shared ReadOnly Title As LocalizableString = "$safeitemname$ Title" + Friend Shared ReadOnly MessageFormat As LocalizableString = "$safeitemname$ '{0}'" + Friend Const Category = "$safeitemname$ Category" + + Friend Shared Rule As New DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, True) + + Public Overrides ReadOnly Property SupportedDiagnostics As ImmutableArray(Of DiagnosticDescriptor) + Get + Return ImmutableArray.Create(Rule) + End Get + End Property + + Public Overrides Sub Initialize(context As AnalysisContext) + End Sub +End Class diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/VisualBasic/Analyzer/VBAnalyzer.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/VisualBasic/Analyzer/VBAnalyzer.vstemplate new file mode 100644 index 000000000..66a43351b --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/VisualBasic/Analyzer/VBAnalyzer.vstemplate @@ -0,0 +1,14 @@ + + + Microsoft.VisualBasic.CodeAnalysis.Analyzer + Analyzer.vb + Analyzer + Create a Visual Basic diagnostic analyzer. + VisualBasic + 10 + + + + Analyzer.vb + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/VisualBasic/CodeFix/CodeFix.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/VisualBasic/CodeFix/CodeFix.vb new file mode 100644 index 000000000..6b749c7e2 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/VisualBasic/CodeFix/CodeFix.vb @@ -0,0 +1,33 @@ +Imports System.Composition +Imports System.Collections.Immutable +Imports System.Threading +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeActions +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Rename +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + + +Public Class $safeitemname$ + Inherits CodeFixProvider + + ' TODO: Replace with actual diagnostic id that should trigger this fix. + Public Const DiagnosticId As String = "$safeitemname$" + + Public NotOverridable Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) + Get + Return ImmutableArray.Create(DiagnosticId) + End Get + End Property + + Public NotOverridable Overrides Function GetFixAllProvider() As FixAllProvider + Return WellKnownFixAllProviders.BatchFixer + End Function + + Public NotOverridable Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task + Throw New NotImplementedException() + End Function +End Class diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/VisualBasic/CodeFix/VBCodeFix.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/VisualBasic/CodeFix/VBCodeFix.vstemplate new file mode 100644 index 000000000..bfa8f7c28 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/VisualBasic/CodeFix/VBCodeFix.vstemplate @@ -0,0 +1,14 @@ + + + Microsoft.VisualBasic.CodeAnalysis.CodeFix + CodeFix.vb + CodeFix + Create a Visual Basic code fix. + VisualBasic + 10 + + + + CodeFix.vb + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/VisualBasic/Refactoring/Refactoring.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/VisualBasic/Refactoring/Refactoring.vb new file mode 100644 index 000000000..4da10df92 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/VisualBasic/Refactoring/Refactoring.vb @@ -0,0 +1,18 @@ +Imports System.Composition +Imports System.Threading +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeActions +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Rename +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + + +Friend Class $safeitemname$ + Inherits CodeRefactoringProvider + + Public NotOverridable Overrides Function ComputeRefactoringsAsync(context As CodeRefactoringContext) As Task + Throw New NotImplementedException() + End Function +End Class diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/VisualBasic/Refactoring/VBRefactoring.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/VisualBasic/Refactoring/VBRefactoring.vstemplate new file mode 100644 index 000000000..d24ec0142 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ItemTemplates/VisualBasic/Refactoring/VBRefactoring.vstemplate @@ -0,0 +1,14 @@ + + + Microsoft.VisualBasic.CodeAnalysis.Refactoring + Refactoring.vb + Refactoring + Create a Visual Basic refactoring. + VisualBasic + 10 + + + + Refactoring.vb + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/CodeRefactoring/Ref/CSharpCodeRefactoring.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/CodeRefactoring/Ref/CSharpCodeRefactoring.vstemplate new file mode 100644 index 000000000..73036a550 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/CodeRefactoring/Ref/CSharpCodeRefactoring.vstemplate @@ -0,0 +1,28 @@ + + + + Code Refactoring (VSIX) + Create a C# refactoring, deployed as a VSIX extension + + CSharp + 2.0 + 951 + Microsoft.CSharp.CodeRefactoring.ClassLib + true + true + CodeRefactoring + true + true + + + + CodeRefactoringProvider.cs + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKAnalyzerTemplateWizard + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/CodeRefactoring/Ref/CodeRefactoring.csproj b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/CodeRefactoring/Ref/CodeRefactoring.csproj new file mode 100644 index 000000000..ef35210ca --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/CodeRefactoring/Ref/CodeRefactoring.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + true + + + + + + + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/CodeRefactoring/Ref/CodeRefactoringProvider.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/CodeRefactoring/Ref/CodeRefactoringProvider.cs new file mode 100644 index 000000000..4d4c7de6d --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/CodeRefactoring/Ref/CodeRefactoringProvider.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Rename; +using Microsoft.CodeAnalysis.Text; + +namespace $safeprojectname$ +{ + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof($saferootidentifiername$CodeRefactoringProvider)), Shared] + internal class $saferootidentifiername$CodeRefactoringProvider : CodeRefactoringProvider + { + public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + // TODO: Replace the following code with your own analysis, generating a CodeAction for each refactoring to offer + + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + + // Find the node at the selection. + var node = root.FindNode(context.Span); + + // Only offer a refactoring if the selected node is a type declaration node. + var typeDecl = node as TypeDeclarationSyntax; + if (typeDecl == null) + { + return; + } + + // For any type declaration node, create a code action to reverse the identifier text. + var action = CodeAction.Create("Reverse type name", c => ReverseTypeNameAsync(context.Document, typeDecl, c)); + + // Register this code action. + context.RegisterRefactoring(action); + } + + private async Task ReverseTypeNameAsync(Document document, TypeDeclarationSyntax typeDecl, CancellationToken cancellationToken) + { + // Produce a reversed version of the type declaration's identifier token. + var identifierToken = typeDecl.Identifier; + var newName = new string(identifierToken.Text.ToCharArray().Reverse().ToArray()); + + // Get the symbol representing the type to be renamed. + var semanticModel = await document.GetSemanticModelAsync(cancellationToken); + var typeSymbol = semanticModel.GetDeclaredSymbol(typeDecl, cancellationToken); + + // Produce a new solution that has all references to that type renamed, including the declaration. + var originalSolution = document.Project.Solution; + var optionSet = originalSolution.Workspace.Options; + var newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(false); + + // Return the new solution with the now-uppercase type name. + return newSolution; + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/CodeRefactoring/Vsix/Deployment.csproj b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/CodeRefactoring/Vsix/Deployment.csproj new file mode 100644 index 000000000..7fd0c7d8b --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/CodeRefactoring/Vsix/Deployment.csproj @@ -0,0 +1,43 @@ + + + + + + net472 + $saferootprojectname$.Vsix + $saferootprojectname$.Vsix + + + + false + false + false + false + false + false + Roslyn + + + + + + + + Program + $(DevEnvDir)devenv.exe + /rootsuffix $(VSSDKTargetPlatformRegRootSuffix) + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/CodeRefactoring/Vsix/Deployment.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/CodeRefactoring/Vsix/Deployment.vstemplate new file mode 100644 index 000000000..1dd0c03de --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/CodeRefactoring/Vsix/Deployment.vstemplate @@ -0,0 +1,28 @@ + + + + Vsix + <No description available> + + CSharp + 2.0 + 1000 + bb967cab-2ca5-4dac-8809-65b2b28a6350 + true + true + Vsix + true + true + + + + source.extension.vsixmanifest + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKVsixTemplateWizardSecondProject + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/CodeRefactoring/Vsix/source.extension.vsixmanifest b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/CodeRefactoring/Vsix/source.extension.vsixmanifest new file mode 100644 index 000000000..5cc094758 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/CodeRefactoring/Vsix/source.extension.vsixmanifest @@ -0,0 +1,21 @@ + + + + + $saferootprojectname$ + This is a sample code refactoring extension for the .NET Compiler Platform ("Roslyn"). + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/ConsoleApplication/CSharpConsoleApplication.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/ConsoleApplication/CSharpConsoleApplication.vstemplate new file mode 100644 index 000000000..c355db12a --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/ConsoleApplication/CSharpConsoleApplication.vstemplate @@ -0,0 +1,36 @@ + + + + Standalone Code Analysis Tool + Create a code analysis command-line application + CodeAnalysisConsole.ico + CSharp + 2.0 + 950 + Microsoft.CSharp.StandaloneCodeAnalysis + true + true + CodeAnalysisApp + true + true + C# + Windows + Linux + macOS + Console + VSSDK + Roslyn + Extensions + + + + Program.cs + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKRootTemplateWizard + + \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/ConsoleApplication/CodeAnalysisConsole.ico b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/ConsoleApplication/CodeAnalysisConsole.ico similarity index 100% rename from src/Templates/VS2015/ProjectTemplates/CSharp/ConsoleApplication/CodeAnalysisConsole.ico rename to src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/ConsoleApplication/CodeAnalysisConsole.ico diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/ConsoleApplication/ConsoleApplication.csproj b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/ConsoleApplication/ConsoleApplication.csproj new file mode 100644 index 000000000..a512ab33d --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/ConsoleApplication/ConsoleApplication.csproj @@ -0,0 +1,16 @@ + + + + Exe + net472 + + + + + + + + + + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/ConsoleApplication/Program.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/ConsoleApplication/Program.cs new file mode 100644 index 000000000..d7659b667 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/ConsoleApplication/Program.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Build.Locator; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.MSBuild; +using Microsoft.CodeAnalysis.Text; + +namespace $safeprojectname$ +{ + class Program + { + static async Task Main(string[] args) + { + // Attempt to set the version of MSBuild. + var visualStudioInstances = MSBuildLocator.QueryVisualStudioInstances().ToArray(); + var instance = visualStudioInstances.Length == 1 + // If there is only one instance of MSBuild on this machine, set that as the one to use. + ? visualStudioInstances[0] + // Handle selecting the version of MSBuild you want to use. + : SelectVisualStudioInstance(visualStudioInstances); + + Console.WriteLine($"Using MSBuild at '{instance.MSBuildPath}' to load projects."); + + // NOTE: Be sure to register an instance with the MSBuildLocator + // before calling MSBuildWorkspace.Create() + // otherwise, MSBuildWorkspace won't MEF compose. + MSBuildLocator.RegisterInstance(instance); + + using (var workspace = MSBuildWorkspace.Create()) + { + // Print message for WorkspaceFailed event to help diagnosing project load failures. + workspace.WorkspaceFailed += (o, e) => Console.WriteLine(e.Diagnostic.Message); + + var solutionPath = args[0]; + Console.WriteLine($"Loading solution '{solutionPath}'"); + + // Attach progress reporter so we print projects as they are loaded. + var solution = await workspace.OpenSolutionAsync(solutionPath, new ConsoleProgressReporter()); + Console.WriteLine($"Finished loading solution '{solutionPath}'"); + + // TODO: Do analysis on the projects in the loaded solution + } + } + + private static VisualStudioInstance SelectVisualStudioInstance(VisualStudioInstance[] visualStudioInstances) + { + Console.WriteLine("Multiple installs of MSBuild detected please select one:"); + for (int i = 0; i < visualStudioInstances.Length; i++) + { + Console.WriteLine($"Instance {i + 1}"); + Console.WriteLine($" Name: {visualStudioInstances[i].Name}"); + Console.WriteLine($" Version: {visualStudioInstances[i].Version}"); + Console.WriteLine($" MSBuild Path: {visualStudioInstances[i].MSBuildPath}"); + } + + while (true) + { + var userResponse = Console.ReadLine(); + if (int.TryParse(userResponse, out int instanceNumber) && + instanceNumber > 0 && + instanceNumber <= visualStudioInstances.Length) + { + return visualStudioInstances[instanceNumber - 1]; + } + Console.WriteLine("Input not accepted, try again."); + } + } + + private class ConsoleProgressReporter : IProgress + { + public void Report(ProjectLoadProgress loadProgress) + { + var projectDisplay = Path.GetFileName(loadProgress.FilePath); + if (loadProgress.TargetFramework != null) + { + projectDisplay += $" ({loadProgress.TargetFramework})"; + } + + Console.WriteLine($"{loadProgress.Operation,-15} {loadProgress.ElapsedTime,-15:m\\:ss\\.fffffff} {projectDisplay}"); + } + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.cs new file mode 100644 index 000000000..9953c1e68 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace $saferootprojectname$ +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class $saferootidentifiername$Analyzer : DiagnosticAnalyzer + { + public const string DiagnosticId = "$saferootidentifiername$"; + + // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat. + // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Localizing%20Analyzers.md for more on localization + private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources)); + private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources)); + private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources)); + private const string Category = "Naming"; + + private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); + + public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } } + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + // TODO: Consider registering other actions that act on syntax instead of or in addition to symbols + // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Analyzer%20Actions%20Semantics.md for more information + context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType); + } + + private static void AnalyzeSymbol(SymbolAnalysisContext context) + { + // TODO: Replace the following code with your own analysis, generating Diagnostic objects for any issues you find + var namedTypeSymbol = (INamedTypeSymbol)context.Symbol; + + // Find just those named type symbols with names containing lowercase letters. + if (namedTypeSymbol.Name.ToCharArray().Any(char.IsLower)) + { + // For all such symbols, produce a diagnostic. + var diagnostic = Diagnostic.Create(Rule, namedTypeSymbol.Locations[0], namedTypeSymbol.Name); + + context.ReportDiagnostic(diagnostic); + } + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.csproj b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.csproj new file mode 100644 index 000000000..7176c81d7 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.csproj @@ -0,0 +1,21 @@ + + + + netstandard2.0 + false + + + *$(MSBuildProjectFile)* + + + + + + + + + + + + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.vstemplate new file mode 100644 index 000000000..6a305f4e9 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/DiagnosticAnalyzer.vstemplate @@ -0,0 +1,30 @@ + + + + DiagnosticAnalyzer + <No description available> + + CSharp + 2.0 + 952 + bb967cab-2ca5-4dac-8809-65b2b28a6350 + true + true + DiagnosticAnalyzer + true + true + + + + DiagnosticAnalyzer.cs + Resources.resx + Resources.Designer.cs + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKAnalyzerTemplateWizard + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/Resources.Designer.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/Resources.Designer.cs new file mode 100644 index 000000000..133bebe15 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/Resources.Designer.cs @@ -0,0 +1,106 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace $saferootprojectname$ +{ + using System; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if (object.ReferenceEquals(resourceMan, null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("$saferootprojectname$.Resources", typeof(Resources).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Type names should be all uppercase.. + /// + internal static string AnalyzerDescription + { + get + { + return ResourceManager.GetString("AnalyzerDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type name '{0}' contains lowercase letters. + /// + internal static string AnalyzerMessageFormat + { + get + { + return ResourceManager.GetString("AnalyzerMessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type name contains lowercase letters. + /// + internal static string AnalyzerTitle + { + get + { + return ResourceManager.GetString("AnalyzerTitle", resourceCulture); + } + } + } +} diff --git a/src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/Resources.resx b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/Resources.resx similarity index 100% rename from src/Templates/VS2015/ProjectTemplates/CSharp/Diagnostic/Analyzer/Resources.resx rename to src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/Resources.resx diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/CodeFix/CodeFixProvider.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/CodeFix/CodeFixProvider.cs new file mode 100644 index 000000000..a1aeca1ed --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/CodeFix/CodeFixProvider.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Rename; +using Microsoft.CodeAnalysis.Text; + +namespace $saferootprojectname$ +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof($saferootidentifiername$CodeFixProvider)), Shared] + public class $saferootidentifiername$CodeFixProvider : CodeFixProvider + { + public sealed override ImmutableArray FixableDiagnosticIds + { + get { return ImmutableArray.Create($saferootidentifiername$Analyzer.DiagnosticId); } + } + + public sealed override FixAllProvider GetFixAllProvider() + { + // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/FixAllProvider.md for more information on Fix All Providers + return WellKnownFixAllProviders.BatchFixer; + } + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + + // TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest + var diagnostic = context.Diagnostics.First(); + var diagnosticSpan = diagnostic.Location.SourceSpan; + + // Find the type declaration identified by the diagnostic. + var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); + + // Register a code action that will invoke the fix. + context.RegisterCodeFix( + CodeAction.Create( + title: CodeFixResources.CodeFixTitle, + createChangedSolution: c => MakeUppercaseAsync(context.Document, declaration, c), + equivalenceKey: nameof(CodeFixResources.CodeFixTitle)), + diagnostic); + } + + private async Task MakeUppercaseAsync(Document document, TypeDeclarationSyntax typeDecl, CancellationToken cancellationToken) + { + // Compute new uppercase name. + var identifierToken = typeDecl.Identifier; + var newName = identifierToken.Text.ToUpperInvariant(); + + // Get the symbol representing the type to be renamed. + var semanticModel = await document.GetSemanticModelAsync(cancellationToken); + var typeSymbol = semanticModel.GetDeclaredSymbol(typeDecl, cancellationToken); + + // Produce a new solution that has all references to that type renamed, including the declaration. + var originalSolution = document.Project.Solution; + var optionSet = originalSolution.Workspace.Options; + var newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(false); + + // Return the new solution with the now-uppercase type name. + return newSolution; + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/CodeFix/CodeFixProvider.csproj b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/CodeFix/CodeFixProvider.csproj new file mode 100644 index 000000000..1c091606e --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/CodeFix/CodeFixProvider.csproj @@ -0,0 +1,18 @@ + + + + netstandard2.0 + false + $saferootprojectname$ + + + + + + + + + + + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/CodeFix/CodeFixProvider.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/CodeFix/CodeFixProvider.vstemplate new file mode 100644 index 000000000..267bf2505 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/CodeFix/CodeFixProvider.vstemplate @@ -0,0 +1,30 @@ + + + + CodeFixProvider + <No description available> + + CSharp + 2.0 + 952 + bb967cab-2ca5-4dac-8809-65b2b28a6350 + true + true + CodeFixProvider + true + true + + + + CodeFixProvider.cs + CodeFixResources.resx + CodeFixResources.Designer.cs + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKCodeFixTemplateWizard + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/CodeFix/CodeFixResources.Designer.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/CodeFix/CodeFixResources.Designer.cs new file mode 100644 index 000000000..07e319a98 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/CodeFix/CodeFixResources.Designer.cs @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace $saferootprojectname$ +{ + using System; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class CodeFixResources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal CodeFixResources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if (object.ReferenceEquals(resourceMan, null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("$saferootprojectname$.CodeFixResources", typeof(CodeFixResources).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Make uppercase. + /// + internal static string CodeFixTitle + { + get + { + return ResourceManager.GetString("CodeFixTitle", resourceCulture); + } + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/CodeFix/CodeFixResources.resx b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/CodeFix/CodeFixResources.resx new file mode 100644 index 000000000..97abe6894 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/CodeFix/CodeFixResources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Make uppercase + The title of the code fix. + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Package/Package.csproj b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Package/Package.csproj new file mode 100644 index 000000000..0240e5a76 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Package/Package.csproj @@ -0,0 +1,40 @@ + + + + netstandard2.0 + false + true + true + + + + $saferootprojectname$ + 1.0.0.0 + $username$ + http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE + http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE + http://ICON_URL_HERE_OR_DELETE_THIS_LINE + http://REPOSITORY_URL_HERE_OR_DELETE_THIS_LINE + false + $saferootprojectname$ + Summary of changes made in this release of the package. + Copyright + $saferootprojectname$, analyzers + true + true + + $(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput + + + + + + + + + + + + + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Package/Package.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Package/Package.vstemplate new file mode 100644 index 000000000..97094dcb6 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Package/Package.vstemplate @@ -0,0 +1,29 @@ + + + + Package + <No description available> + + CSharp + 2.0 + 952 + bb967cab-2ca5-4dac-8809-65b2b28a6350 + true + true + Package + true + true + + + + install.ps1 + uninstall.ps1 + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKPackageTemplateWizard + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Package/install.ps1 b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Package/install.ps1 new file mode 100644 index 000000000..c1c3d8822 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Package/install.ps1 @@ -0,0 +1,58 @@ +param($installPath, $toolsPath, $package, $project) + +if($project.Object.SupportsPackageDependencyResolution) +{ + if($project.Object.SupportsPackageDependencyResolution()) + { + # Do not install analyzers via install.ps1, instead let the project system handle it. + return + } +} + +$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve + +foreach($analyzersPath in $analyzersPaths) +{ + if (Test-Path $analyzersPath) + { + # Install the language agnostic analyzers. + foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) + } + } + } +} + +# $project.Type gives the language name like (C# or VB.NET) +$languageFolder = "" +if($project.Type -eq "C#") +{ + $languageFolder = "cs" +} +if($project.Type -eq "VB.NET") +{ + $languageFolder = "vb" +} +if($languageFolder -eq "") +{ + return +} + +foreach($analyzersPath in $analyzersPaths) +{ + # Install language specific analyzers. + $languageAnalyzersPath = join-path $analyzersPath $languageFolder + if (Test-Path $languageAnalyzersPath) + { + foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) + } + } + } +} \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Package/uninstall.ps1 b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Package/uninstall.ps1 new file mode 100644 index 000000000..65a862370 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Package/uninstall.ps1 @@ -0,0 +1,65 @@ +param($installPath, $toolsPath, $package, $project) + +if($project.Object.SupportsPackageDependencyResolution) +{ + if($project.Object.SupportsPackageDependencyResolution()) + { + # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it. + return + } +} + +$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve + +foreach($analyzersPath in $analyzersPaths) +{ + # Uninstall the language agnostic analyzers. + if (Test-Path $analyzersPath) + { + foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) + } + } + } +} + +# $project.Type gives the language name like (C# or VB.NET) +$languageFolder = "" +if($project.Type -eq "C#") +{ + $languageFolder = "cs" +} +if($project.Type -eq "VB.NET") +{ + $languageFolder = "vb" +} +if($languageFolder -eq "") +{ + return +} + +foreach($analyzersPath in $analyzersPaths) +{ + # Uninstall language specific analyzers. + $languageAnalyzersPath = join-path $analyzersPath $languageFolder + if (Test-Path $languageAnalyzersPath) + { + foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + try + { + $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) + } + catch + { + + } + } + } + } +} \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Test.csproj b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Test.csproj new file mode 100644 index 000000000..3ff076d88 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Test.csproj @@ -0,0 +1,27 @@ + + + + netcoreapp2.0 + + true + true + + + + + + + + + + + + + + + + + + + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Test.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Test.vstemplate new file mode 100644 index 000000000..d9d7343d3 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Test.vstemplate @@ -0,0 +1,43 @@ + + + + TestProject + <No description available> + + CSharp + 2.0 + 1000 + bb967cab-2ca5-4dac-8809-65b2b28a6351 + true + true + TestProject + true + true + + + + UnitTests.cs + + + Verifiers\CSAnalyzer.cs + Verifiers\CSAnalyzer+Test.cs + Verifiers\CSCodeFix.cs + Verifiers\CSCodeFix+Test.cs + Verifiers\CSRefactoring.cs + Verifiers\CSRefactoring+Test.cs + Verifiers\CSHelper.cs + Verifiers\VBAnalyzer.cs + Verifiers\VBAnalyzer+Test.cs + Verifiers\VBCodeFix.cs + Verifiers\VBCodeFix+Test.cs + Verifiers\VBRefactoring.cs + Verifiers\VBRefactoring+Test.cs + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKTestTemplateWizard + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/UnitTests.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/UnitTests.cs new file mode 100644 index 000000000..abc9124d9 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/UnitTests.cs @@ -0,0 +1,59 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Threading.Tasks; +using VerifyCS = $safeprojectname$.CSharpCodeFixVerifier< + $saferootprojectname$.$saferootidentifiername$Analyzer, + $saferootprojectname$.$saferootidentifiername$CodeFixProvider>; + +namespace $safeprojectname$ +{ + [TestClass] + public class $saferootidentifiername$UnitTest + { + //No diagnostics expected to show up + [TestMethod] + public async Task TestMethod1() + { + var test = @""; + + await VerifyCS.VerifyAnalyzerAsync(test); + } + + //Diagnostic and CodeFix both triggered and checked for + [TestMethod] + public async Task TestMethod2() + { + var test = @" + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Diagnostics; + + namespace ConsoleApplication1 + { + class {|#0:TypeName|} + { + } + }"; + + var fixtest = @" + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Diagnostics; + + namespace ConsoleApplication1 + { + class TYPENAME + { + } + }"; + + var expected = VerifyCS.Diagnostic("$saferootidentifiername$").WithLocation(0).WithArguments("TypeName"); + await VerifyCS.VerifyCodeFixAsync(test, expected, fixtest); + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSAnalyzer+Test.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSAnalyzer+Test.cs new file mode 100644 index 000000000..0fd3a85b0 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSAnalyzer+Test.cs @@ -0,0 +1,26 @@ +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace $safeprojectname$ +{ + public static partial class CSharpAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + { + public class Test : CSharpAnalyzerTest + { + public Test() + { + SolutionTransforms.Add((solution, projectId) => + { + var compilationOptions = solution.GetProject(projectId).CompilationOptions; + compilationOptions = compilationOptions.WithSpecificDiagnosticOptions( + compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)); + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions); + + return solution; + }); + } + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSAnalyzer.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSAnalyzer.cs new file mode 100644 index 000000000..a9c14eb1d --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSAnalyzer.cs @@ -0,0 +1,38 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace $safeprojectname$ +{ + public static partial class CSharpAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + { + /// + public static DiagnosticResult Diagnostic() + => CSharpAnalyzerVerifier.Diagnostic(); + + /// + public static DiagnosticResult Diagnostic(string diagnosticId) + => CSharpAnalyzerVerifier.Diagnostic(diagnosticId); + + /// + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => CSharpAnalyzerVerifier.Diagnostic(descriptor); + + /// + public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new Test + { + TestCode = source, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSCodeFix+Test.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSCodeFix+Test.cs new file mode 100644 index 000000000..f0daec8be --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSCodeFix+Test.cs @@ -0,0 +1,28 @@ +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace $safeprojectname$ +{ + public static partial class CSharpCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + public class Test : CSharpCodeFixTest + { + public Test() + { + SolutionTransforms.Add((solution, projectId) => + { + var compilationOptions = solution.GetProject(projectId).CompilationOptions; + compilationOptions = compilationOptions.WithSpecificDiagnosticOptions( + compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)); + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions); + + return solution; + }); + } + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSCodeFix.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSCodeFix.cs new file mode 100644 index 000000000..67fb48696 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSCodeFix.cs @@ -0,0 +1,61 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace $safeprojectname$ +{ + public static partial class CSharpCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + /// + public static DiagnosticResult Diagnostic() + => CSharpCodeFixVerifier.Diagnostic(); + + /// + public static DiagnosticResult Diagnostic(string diagnosticId) + => CSharpCodeFixVerifier.Diagnostic(diagnosticId); + + /// + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => CSharpCodeFixVerifier.Diagnostic(descriptor); + + /// + public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new Test + { + TestCode = source, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + + /// + public static async Task VerifyCodeFixAsync(string source, string fixedSource) + => await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + + /// + public static async Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource) + => await VerifyCodeFixAsync(source, new[] { expected }, fixedSource); + + /// + public static async Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSHelper.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSHelper.cs new file mode 100644 index 000000000..649286003 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSHelper.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace $safeprojectname$ +{ + internal static class CSharpVerifierHelper + { + /// + /// By default, the compiler reports diagnostics for nullable reference types at + /// , and the analyzer test framework defaults to only validating + /// diagnostics at . This map contains all compiler diagnostic IDs + /// related to nullability mapped to , which is then used to enable all + /// of these warnings for default validation during analyzer and code fix tests. + /// + internal static ImmutableDictionary NullableWarnings { get; } = GetNullableWarningsFromCompiler(); + + private static ImmutableDictionary GetNullableWarningsFromCompiler() + { + string[] args = { "/warnaserror:nullable" }; + var commandLineArguments = CSharpCommandLineParser.Default.Parse(args, baseDirectory: Environment.CurrentDirectory, sdkDirectory: Environment.CurrentDirectory); + var nullableWarnings = commandLineArguments.CompilationOptions.SpecificDiagnosticOptions; + + // Workaround for https://github.com/dotnet/roslyn/issues/41610 + nullableWarnings = nullableWarnings + .SetItem("CS8632", ReportDiagnostic.Error) + .SetItem("CS8669", ReportDiagnostic.Error); + + return nullableWarnings; + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSRefactoring+Test.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSRefactoring+Test.cs new file mode 100644 index 000000000..6d657580b --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSRefactoring+Test.cs @@ -0,0 +1,26 @@ +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace $safeprojectname$ +{ + public static partial class CSharpCodeRefactoringVerifier + where TCodeRefactoring : CodeRefactoringProvider, new() + { + public class Test : CSharpCodeRefactoringTest + { + public Test() + { + SolutionTransforms.Add((solution, projectId) => + { + var compilationOptions = solution.GetProject(projectId).CompilationOptions; + compilationOptions = compilationOptions.WithSpecificDiagnosticOptions( + compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)); + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions); + + return solution; + }); + } + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSRefactoring.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSRefactoring.cs new file mode 100644 index 000000000..8d2aafbe6 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/CSRefactoring.cs @@ -0,0 +1,36 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Testing; + +namespace $safeprojectname$ +{ + public static partial class CSharpCodeRefactoringVerifier + where TCodeRefactoring : CodeRefactoringProvider, new() + { + /// + public static async Task VerifyRefactoringAsync(string source, string fixedSource) + { + await VerifyRefactoringAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + } + + /// + public static async Task VerifyRefactoringAsync(string source, DiagnosticResult expected, string fixedSource) + { + await VerifyRefactoringAsync(source, new[] { expected }, fixedSource); + } + + /// + public static async Task VerifyRefactoringAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/VBAnalyzer+Test.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/VBAnalyzer+Test.cs new file mode 100644 index 000000000..2e1035e47 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/VBAnalyzer+Test.cs @@ -0,0 +1,17 @@ +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.VisualBasic.Testing; + +namespace $safeprojectname$ +{ + public static partial class VisualBasicAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + { + public class Test : VisualBasicAnalyzerTest + { + public Test() + { + } + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/VBAnalyzer.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/VBAnalyzer.cs new file mode 100644 index 000000000..0b9389014 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/VBAnalyzer.cs @@ -0,0 +1,38 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.VisualBasic.Testing; + +namespace $safeprojectname$ +{ + public static partial class VisualBasicAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + { + /// + public static DiagnosticResult Diagnostic() + => VisualBasicAnalyzerVerifier.Diagnostic(); + + /// + public static DiagnosticResult Diagnostic(string diagnosticId) + => VisualBasicAnalyzerVerifier.Diagnostic(diagnosticId); + + /// + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => VisualBasicAnalyzerVerifier.Diagnostic(descriptor); + + /// + public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new Test + { + TestCode = source, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/VBCodeFix+Test.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/VBCodeFix+Test.cs new file mode 100644 index 000000000..419feadcf --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/VBCodeFix+Test.cs @@ -0,0 +1,16 @@ +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.VisualBasic.Testing; + +namespace $safeprojectname$ +{ + public static partial class VisualBasicCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + public class Test : VisualBasicCodeFixTest + { + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/VBCodeFix.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/VBCodeFix.cs new file mode 100644 index 000000000..6a8a5a9c6 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/VBCodeFix.cs @@ -0,0 +1,61 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.VisualBasic.Testing; + +namespace $safeprojectname$ +{ + public static partial class VisualBasicCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + /// + public static DiagnosticResult Diagnostic() + => VisualBasicCodeFixVerifier.Diagnostic(); + + /// + public static DiagnosticResult Diagnostic(string diagnosticId) + => VisualBasicCodeFixVerifier.Diagnostic(diagnosticId); + + /// + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => VisualBasicCodeFixVerifier.Diagnostic(descriptor); + + /// + public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new Test + { + TestCode = source, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + + /// + public static async Task VerifyCodeFixAsync(string source, string fixedSource) + => await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + + /// + public static async Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource) + => await VerifyCodeFixAsync(source, new[] { expected }, fixedSource); + + /// + public static async Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/VBRefactoring+Test.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/VBRefactoring+Test.cs new file mode 100644 index 000000000..8f6695974 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/VBRefactoring+Test.cs @@ -0,0 +1,14 @@ +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.VisualBasic.Testing; + +namespace $safeprojectname$ +{ + public static partial class VisualBasicCodeRefactoringVerifier + where TCodeRefactoring : CodeRefactoringProvider, new() + { + public class Test : VisualBasicCodeRefactoringTest + { + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/VBRefactoring.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/VBRefactoring.cs new file mode 100644 index 000000000..bbe356ff0 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Test/Verifiers/VBRefactoring.cs @@ -0,0 +1,36 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Testing; + +namespace $safeprojectname$ +{ + public static partial class VisualBasicCodeRefactoringVerifier + where TCodeRefactoring : CodeRefactoringProvider, new() + { + /// + public static async Task VerifyRefactoringAsync(string source, string fixedSource) + { + await VerifyRefactoringAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + } + + /// + public static async Task VerifyRefactoringAsync(string source, DiagnosticResult expected, string fixedSource) + { + await VerifyRefactoringAsync(source, new[] { expected }, fixedSource); + } + + /// + public static async Task VerifyRefactoringAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Vsix/Deployment.csproj b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Vsix/Deployment.csproj new file mode 100644 index 000000000..7fd0c7d8b --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Vsix/Deployment.csproj @@ -0,0 +1,43 @@ + + + + + + net472 + $saferootprojectname$.Vsix + $saferootprojectname$.Vsix + + + + false + false + false + false + false + false + Roslyn + + + + + + + + Program + $(DevEnvDir)devenv.exe + /rootsuffix $(VSSDKTargetPlatformRegRootSuffix) + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Vsix/Deployment.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Vsix/Deployment.vstemplate new file mode 100644 index 000000000..7fabe36bd --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Vsix/Deployment.vstemplate @@ -0,0 +1,28 @@ + + + + Vsix + <No description available> + + CSharp + 2.0 + 1000 + bb967cab-2ca5-4dac-8809-65b2b28a6350 + true + true + Vsix + true + true + + + + source.extension.vsixmanifest + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKVsixTemplateWizardThirdProject + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Vsix/source.extension.vsixmanifest b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Vsix/source.extension.vsixmanifest new file mode 100644 index 000000000..5412f6779 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Vsix/source.extension.vsixmanifest @@ -0,0 +1,24 @@ + + + + + $saferootprojectname$ + This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn"). + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/CodeRefactoring.vbproj b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/CodeRefactoring.vbproj new file mode 100644 index 000000000..c1a51d90f --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/CodeRefactoring.vbproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + true + + + + + + + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/CodeRefactoringProvider.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/CodeRefactoringProvider.vb new file mode 100644 index 000000000..93afc6df8 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/CodeRefactoringProvider.vb @@ -0,0 +1,56 @@ +Imports System.Collections.Generic +Imports System.Composition +Imports System.Linq +Imports System.Threading +Imports System.Threading.Tasks +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeActions +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Microsoft.CodeAnalysis.Rename +Imports Microsoft.CodeAnalysis.Text + + +Friend Class $saferootidentifiername$CodeRefactoringProvider + Inherits CodeRefactoringProvider + + Public NotOverridable Overrides Async Function ComputeRefactoringsAsync(context As CodeRefactoringContext) As Task + ' TODO: Replace the following code with your own analysis, generating a CodeAction for each refactoring to offer + + Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + + ' Find the node at the selection. + Dim node = root.FindNode(context.Span) + + ' Only offer a refactoring if the selected node is a type statement node. + Dim typeDecl = TryCast(node, TypeStatementSyntax) + If typeDecl Is Nothing Then + Return + End If + + ' For any type statement node, create a code action to reverse the identifier text. + Dim action = CodeAction.Create("Reverse type name", Function(c) ReverseTypeNameAsync(context.Document, typeDecl, c)) + + ' Register this code action. + context.RegisterRefactoring(action) + End Function + + Private Async Function ReverseTypeNameAsync(document As Document, typeStmt As TypeStatementSyntax, cancellationToken As CancellationToken) As Task(Of Solution) + ' Produce a reversed version of the type statement's identifier token. + Dim identifierToken = typeStmt.Identifier + Dim newName = New String(identifierToken.Text.ToCharArray.Reverse.ToArray) + + ' Get the symbol representing the type to be renamed. + Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken) + Dim typeSymbol = semanticModel.GetDeclaredSymbol(typeStmt, cancellationToken) + + ' Produce a new solution that has all references to that type renamed, including the declaration. + Dim originalSolution = document.Project.Solution + Dim optionSet = originalSolution.Workspace.Options + Dim newSolution = Await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(False) + + ' Return the new solution with the now-uppercase type name. + Return newSolution + End Function +End Class diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/VBCodeRefactoring.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/VBCodeRefactoring.vstemplate new file mode 100644 index 000000000..63b41a052 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Ref/VBCodeRefactoring.vstemplate @@ -0,0 +1,28 @@ + + + + Code Refactoring (VSIX) + Create a Visual Basic refactoring, deployed as a VSIX extension + + VisualBasic + 2.0 + 1000 + Microsoft.VisualBasic.CodeRefactoring.ClassLib + true + true + CodeRefactoring + true + true + + + + CodeRefactoringProvider.vb + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKAnalyzerTemplateWizard + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/AssemblyInfo.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/AssemblyInfo.vb new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/AssemblyInfo.vb @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/Deployment.vbproj b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/Deployment.vbproj new file mode 100644 index 000000000..7fd0c7d8b --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/Deployment.vbproj @@ -0,0 +1,43 @@ + + + + + + net472 + $saferootprojectname$.Vsix + $saferootprojectname$.Vsix + + + + false + false + false + false + false + false + Roslyn + + + + + + + + Program + $(DevEnvDir)devenv.exe + /rootsuffix $(VSSDKTargetPlatformRegRootSuffix) + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/Deployment.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/Deployment.vstemplate new file mode 100644 index 000000000..8c76e0aef --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/Deployment.vstemplate @@ -0,0 +1,29 @@ + + + + Vsix + <No description available> + + VisualBasic + 2.0 + 1000 + bb967cab-2ca5-4dac-8809-65b2b28a6350 + true + true + Vsix + true + true + + + + source.extension.vsixmanifest + AssemblyInfo.vb + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKVsixTemplateWizardSecondProject + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/source.extension.vsixmanifest b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/source.extension.vsixmanifest new file mode 100644 index 000000000..0ee2b5feb --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/CodeRefactoring/Vsix/source.extension.vsixmanifest @@ -0,0 +1,21 @@ + + + + + $saferootprojectname$ + This is a sample code refactoring extension for the .NET Compiler Platform ("Roslyn"). + + + + + + + + + + + + + + + diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/CodeAnalysisConsole.ico b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/ConsoleApplication/CodeAnalysisConsole.ico similarity index 100% rename from src/Templates/VS2015/ProjectTemplates/VisualBasic/ConsoleApplication/CodeAnalysisConsole.ico rename to src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/ConsoleApplication/CodeAnalysisConsole.ico diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/ConsoleApplication/ConsoleApplication.vbproj b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/ConsoleApplication/ConsoleApplication.vbproj new file mode 100644 index 000000000..f47446e78 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/ConsoleApplication/ConsoleApplication.vbproj @@ -0,0 +1,17 @@ + + + + Exe + $safeprojectname$ + net472 + + + + + + + + + + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/ConsoleApplication/Module1.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/ConsoleApplication/Module1.vb new file mode 100644 index 000000000..dd9135674 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/ConsoleApplication/Module1.vb @@ -0,0 +1,80 @@ +Imports System +Imports System.Collections.Generic +Imports System.IO +Imports System.Linq +Imports System.Text +Imports System.Threading.Tasks +Imports Microsoft.Build.Locator +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Microsoft.CodeAnalysis.MSBuild +Imports Microsoft.CodeAnalysis.Text + +Module $safeprojectname$ + + Sub Main(args As String()) + ' Attempt to set the version of MSBuild. + Dim visualStudioInstances = MSBuildLocator.QueryVisualStudioInstances().ToArray() + Dim instance = If(visualStudioInstances.Length = 1, visualStudioInstances(0), SelectVisualStudioInstance(visualStudioInstances)) + + Console.WriteLine($"Using MSBuild at '{instance.MSBuildPath}' to load projects.") + + ' NOTE: Be sure to register an instance with the MSBuildLocator + ' before calling MSBuildWorkspace.Create() + ' otherwise, MSBuildWorkspace won't MEF compose. + MSBuildLocator.RegisterInstance(instance) + + Using workspace = MSBuildWorkspace.Create() + ' Print message for WorkspaceFailed event to help diagnosing project load failures. + AddHandler workspace.WorkspaceFailed, Sub(o, e) + Console.WriteLine(e.Diagnostic.Message) + End Sub + + Dim solutionPath = args(0) + Console.WriteLine($"Loading solution '{solutionPath}'") + + ' Attach progress reporter so we print projects as they are loaded. + Dim solution = workspace.OpenSolutionAsync(solutionPath, New ConsoleProgressReporter()).GetAwaiter.GetResult + Console.WriteLine($"Finished loading solution '{solutionPath}'") + + ' TODO: Do analysis on the projects in the loaded solution + End Using + End Sub + + Function SelectVisualStudioInstance(visualStudioInstances As VisualStudioInstance()) As VisualStudioInstance + Console.WriteLine("Multiple installs of MSBuild detected please select one:") + For index = 0 To visualStudioInstances.Length + Console.WriteLine($"Instance {index + 1}") + Console.WriteLine($" Name: {visualStudioInstances(index).Name}") + Console.WriteLine($" Version: {visualStudioInstances(index).Version}") + Console.WriteLine($" MSBuild Path: {visualStudioInstances(index).MSBuildPath}") + Next + + While True + Dim userResponse = Console.ReadLine() + Dim instanceNumber As Integer = 0 + If Integer.TryParse(userResponse, instanceNumber) AndAlso instanceNumber > 0 AndAlso instanceNumber <= visualStudioInstances.Length Then + Return visualStudioInstances(instanceNumber - 1) + End If + Console.WriteLine("Input not accepted, try again.") + End While + + Return Nothing + End Function + + Private Class ConsoleProgressReporter + Implements IProgress(Of ProjectLoadProgress) + + Public Sub Report(loadProgress As ProjectLoadProgress) Implements IProgress(Of ProjectLoadProgress).Report + Dim projectDisplay = Path.GetFileName(loadProgress.FilePath) + If loadProgress.TargetFramework IsNot Nothing Then + projectDisplay += $" ({loadProgress.TargetFramework})" + End If + + Console.WriteLine($"{loadProgress.Operation,-15} {loadProgress.ElapsedTime,-15:m\:ss\.fffffff} {projectDisplay}") + End Sub + End Class + +End Module diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/ConsoleApplication/VBConsoleApplication.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/ConsoleApplication/VBConsoleApplication.vstemplate new file mode 100644 index 000000000..3987c8583 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/ConsoleApplication/VBConsoleApplication.vstemplate @@ -0,0 +1,36 @@ + + + + Standalone Code Analysis Tool + Create a code analysis command-line application + CodeAnalysisConsole.ico + VisualBasic + 2.0 + 950 + Microsoft.VisualBasic.StandaloneCodeAnalysis + true + true + CodeAnalysisApp + true + true + Visual Basic + Windows + Linux + macOS + Console + VSSDK + Roslyn + Extensions + + + + Module1.vb + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKRootTemplateWizard + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Diagnostic.vbproj b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Diagnostic.vbproj new file mode 100644 index 000000000..2101571a5 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Diagnostic.vbproj @@ -0,0 +1,21 @@ + + + + netstandard2.0 + false + + + *$(MSBuildProjectFile)* + + + + + + + + + + + + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/DiagnosticAnalyzer.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/DiagnosticAnalyzer.vb new file mode 100644 index 000000000..c4bb2b478 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/DiagnosticAnalyzer.vb @@ -0,0 +1,54 @@ +Imports System +Imports System.Collections.Generic +Imports System.Collections.Immutable +Imports System.Linq +Imports System.Threading +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Microsoft.CodeAnalysis.Diagnostics + + +Public Class $saferootidentifiername$Analyzer + Inherits DiagnosticAnalyzer + + Public Const DiagnosticId = "$saferootidentifiername$" + + ' You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat. + ' See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Localizing%20Analyzers.md for more on localization + Private Shared ReadOnly Title As LocalizableString = New LocalizableResourceString(NameOf(My.Resources.AnalyzerTitle), My.Resources.ResourceManager, GetType(My.Resources.Resources)) + Private Shared ReadOnly MessageFormat As LocalizableString = New LocalizableResourceString(NameOf(My.Resources.AnalyzerMessageFormat), My.Resources.ResourceManager, GetType(My.Resources.Resources)) + Private Shared ReadOnly Description As LocalizableString = New LocalizableResourceString(NameOf(My.Resources.AnalyzerDescription), My.Resources.ResourceManager, GetType(My.Resources.Resources)) + Private Const Category = "Naming" + + Private Shared ReadOnly Rule As New DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault:=True, description:=Description) + + Public Overrides ReadOnly Property SupportedDiagnostics As ImmutableArray(Of DiagnosticDescriptor) + Get + Return ImmutableArray.Create(Rule) + End Get + End Property + + Public Overrides Sub Initialize(context As AnalysisContext) + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None) + context.EnableConcurrentExecution() + + ' TODO: Consider registering other actions that act on syntax instead of or in addition to symbols + ' See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Analyzer%20Actions%20Semantics.md for more information + context.RegisterSymbolAction(AddressOf AnalyzeSymbol, SymbolKind.NamedType) + End Sub + + Private Sub AnalyzeSymbol(context As SymbolAnalysisContext) + ' TODO: Replace the following code with your own analysis, generating Diagnostic objects for any issues you find + + Dim namedTypeSymbol = CType(context.Symbol, INamedTypeSymbol) + + ' Find just those named type symbols with names containing lowercase letters. + If namedTypeSymbol.Name.ToCharArray.Any(AddressOf Char.IsLower) Then + ' For all such symbols, produce a diagnostic. + Dim diag = Diagnostic.Create(Rule, namedTypeSymbol.Locations(0), namedTypeSymbol.Name) + + context.ReportDiagnostic(diag) + End If + End Sub +End Class diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/DiagnosticAnalyzer.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/DiagnosticAnalyzer.vstemplate new file mode 100644 index 000000000..632f6de03 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/DiagnosticAnalyzer.vstemplate @@ -0,0 +1,30 @@ + + + + DiagnosticAnalyzer + <No description available> + + VisualBasic + 2.0 + 1000 + bb967cab-2ca5-4dac-8809-65b2b28a6350 + true + true + DiagnosticAnalyzer + true + true + + + + DiagnosticAnalyzer.vb + Resources.resx + Resources.Designer.vb + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKAnalyzerTemplateWizard + + \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Resources.Designer.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Resources.Designer.vb similarity index 100% rename from src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Resources.Designer.vb rename to src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Resources.Designer.vb diff --git a/src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Resources.resx b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Resources.resx similarity index 100% rename from src/Templates/VS2017/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Resources.resx rename to src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/Resources.resx diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/CodeFix/CodeFixProvider.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/CodeFix/CodeFixProvider.vb new file mode 100644 index 000000000..8c3d7308e --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/CodeFix/CodeFixProvider.vb @@ -0,0 +1,68 @@ +Imports System +Imports System.Collections.Generic +Imports System.Collections.Immutable +Imports System.Composition +Imports System.Linq +Imports System.Threading +Imports System.Threading.Tasks +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.CodeActions +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Microsoft.CodeAnalysis.Rename +Imports Microsoft.CodeAnalysis.Text + + +Public Class $saferootidentifiername$CodeFixProvider + Inherits CodeFixProvider + + Public NotOverridable Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) + Get + Return ImmutableArray.Create($saferootidentifiername$Analyzer.DiagnosticId) + End Get + End Property + + Public NotOverridable Overrides Function GetFixAllProvider() As FixAllProvider + ' See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/FixAllProvider.md for more information on Fix All Providers + Return WellKnownFixAllProviders.BatchFixer + End Function + + Public NotOverridable Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task + Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False) + + ' TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest + + Dim diagnostic = context.Diagnostics.First() + Dim diagnosticSpan = diagnostic.Location.SourceSpan + + ' Find the type statement identified by the diagnostic. + Dim declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType(Of TypeStatementSyntax)().First() + + ' Register a code action that will invoke the fix. + context.RegisterCodeFix( + CodeAction.Create( + title:=My.Resources.CodeFixTitle, + createChangedSolution:=Function(c) MakeUppercaseAsync(context.Document, declaration, c), + equivalenceKey:=NameOf(My.Resources.CodeFixTitle)), + diagnostic) + End Function + + Private Async Function MakeUppercaseAsync(document As Document, typeStmt As TypeStatementSyntax, cancellationToken As CancellationToken) As Task(Of Solution) + ' Compute new uppercase name. + Dim identifierToken = typeStmt.Identifier + Dim newName = identifierToken.Text.ToUpperInvariant() + + ' Get the symbol representing the type to be renamed. + Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken) + Dim typeSymbol = semanticModel.GetDeclaredSymbol(typeStmt, cancellationToken) + + ' Produce a new solution that has all references to that type renamed, including the declaration. + Dim originalSolution = document.Project.Solution + Dim optionSet = originalSolution.Workspace.Options + Dim newSolution = Await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(False) + + ' Return the new solution with the now-uppercase type name. + Return newSolution + End Function +End Class diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/CodeFix/CodeFixProvider.vbproj b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/CodeFix/CodeFixProvider.vbproj new file mode 100644 index 000000000..448fff539 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/CodeFix/CodeFixProvider.vbproj @@ -0,0 +1,18 @@ + + + + netstandard2.0 + false + $saferootprojectname$ + + + + + + + + + + + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/CodeFix/CodeFixProvider.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/CodeFix/CodeFixProvider.vstemplate new file mode 100644 index 000000000..113c0562c --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/CodeFix/CodeFixProvider.vstemplate @@ -0,0 +1,30 @@ + + + + CodeFixProvider + <No description available> + + VisualBasic + 2.0 + 1000 + bb967cab-2ca5-4dac-8809-65b2b28a6350 + true + true + CodeFixProvider + true + true + + + + CodeFixProvider.vb + CodeFixResources.resx + CodeFixResources.Designer.vb + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKCodeFixTemplateWizard + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/CodeFix/CodeFixResources.Designer.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/CodeFix/CodeFixResources.Designer.vb new file mode 100644 index 000000000..ea770a00d --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/CodeFix/CodeFixResources.Designer.vb @@ -0,0 +1,73 @@ +'------------------------------------------------------------------------------ +' +' This code was generated by a tool. +' Runtime Version:4.0.30319.0 +' +' Changes to this file may cause incorrect behavior and will be lost if +' the code is regenerated. +' +'------------------------------------------------------------------------------ + +Option Strict On +Option Explicit On + +Imports System +Imports System.Reflection + +Namespace My.Resources + + 'This class was auto-generated by the StronglyTypedResourceBuilder + 'class via a tool like ResGen or Visual Studio. + 'To add or remove a member, edit your .ResX file then rerun ResGen + 'with the /str option, or rebuild your VS project. + ''' + ''' A strongly-typed resource class, for looking up localized strings, etc. + ''' + + Friend Module CodeFixResources + + Private resourceMan As Global.System.Resources.ResourceManager + + Private resourceCulture As Global.System.Globalization.CultureInfo + + ''' + ''' Returns the cached ResourceManager instance used by this class. + ''' + + Friend ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager + Get + If Object.ReferenceEquals(resourceMan, Nothing) Then + Dim temp As Global.System.Resources.ResourceManager = New Global.System.Resources.ResourceManager("$saferootprojectname$.CodeFixResources", GetType(CodeFixResources).GetTypeInfo.Assembly) + resourceMan = temp + End If + Return resourceMan + End Get + End Property + + ''' + ''' Overrides the current thread's CurrentUICulture property for all + ''' resource lookups using this strongly typed resource class. + ''' + + Friend Property Culture() As Global.System.Globalization.CultureInfo + Get + Return resourceCulture + End Get + Set + resourceCulture = Value + End Set + End Property + + ''' + ''' Looks up a localized string similar to Make uppercase. + ''' + Friend ReadOnly Property CodeFixTitle() As String + Get + Return ResourceManager.GetString("CodeFixTitle", resourceCulture) + End Get + End Property + End Module +End Namespace diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/CodeFix/CodeFixResources.resx b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/CodeFix/CodeFixResources.resx new file mode 100644 index 000000000..d7172bdc7 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/CodeFix/CodeFixResources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Make uppercase + The title of the code fix. + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Package/Package.vbproj b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Package/Package.vbproj new file mode 100644 index 000000000..b271b4fe2 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Package/Package.vbproj @@ -0,0 +1,40 @@ + + + + netstandard2.0 + false + true + true + + + + $saferootprojectname$ + 1.0.0.0 + $username$ + http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE + http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE + http://ICON_URL_HERE_OR_DELETE_THIS_LINE + http://REPOSITORY_URL_HERE_OR_DELETE_THIS_LINE + false + $saferootprojectname$ + Summary of changes made in this release of the package. + Copyright + $saferootprojectname$, analyzers + true + true + + $(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput + + + + + + + + + + + + + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Package/Package.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Package/Package.vstemplate new file mode 100644 index 000000000..fdf061550 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Package/Package.vstemplate @@ -0,0 +1,29 @@ + + + + Package + <No description available> + + VisualBasic + 2.0 + 1000 + bb967cab-2ca5-4dac-8809-65b2b28a6350 + true + true + Package + true + true + + + + install.ps1 + uninstall.ps1 + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKPackageTemplateWizard + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Package/install.ps1 b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Package/install.ps1 new file mode 100644 index 000000000..9e3fbbf48 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Package/install.ps1 @@ -0,0 +1,58 @@ +param($installPath, $toolsPath, $package, $project) + +if($project.Object.SupportsPackageDependencyResolution) +{ + if($project.Object.SupportsPackageDependencyResolution()) + { + # Do not install analyzers via install.ps1, instead let the project system handle it. + return + } +} + +$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve + +foreach($analyzersPath in $analyzersPaths) +{ + if (Test-Path $analyzersPath) + { + # Install the language agnostic analyzers. + foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) + } + } + } +} + +# $project.Type gives the language name like (C# or VB.NET) +$languageFolder = "" +if($project.Type -eq "C#") +{ + $languageFolder = "cs" +} +if($project.Type -eq "VB.NET") +{ + $languageFolder = "vb" +} +if($languageFolder -eq "") +{ + return +} + +foreach($analyzersPath in $analyzersPaths) +{ + # Install language specific analyzers. + $languageAnalyzersPath = join-path $analyzersPath $languageFolder + if (Test-Path $languageAnalyzersPath) + { + foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) + } + } + } +} \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Package/uninstall.ps1 b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Package/uninstall.ps1 new file mode 100644 index 000000000..7d9c8cc1d --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Package/uninstall.ps1 @@ -0,0 +1,65 @@ +param($installPath, $toolsPath, $package, $project) + +if($project.Object.SupportsPackageDependencyResolution) +{ + if($project.Object.SupportsPackageDependencyResolution()) + { + # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it. + return + } +} + +$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve + +foreach($analyzersPath in $analyzersPaths) +{ + # Uninstall the language agnostic analyzers. + if (Test-Path $analyzersPath) + { + foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) + } + } + } +} + +# $project.Type gives the language name like (C# or VB.NET) +$languageFolder = "" +if($project.Type -eq "C#") +{ + $languageFolder = "cs" +} +if($project.Type -eq "VB.NET") +{ + $languageFolder = "vb" +} +if($languageFolder -eq "") +{ + return +} + +foreach($analyzersPath in $analyzersPaths) +{ + # Uninstall language specific analyzers. + $languageAnalyzersPath = join-path $analyzersPath $languageFolder + if (Test-Path $languageAnalyzersPath) + { + foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + try + { + $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) + } + catch + { + + } + } + } + } +} \ No newline at end of file diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/CodeFixVerifier.Helper.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/CodeFixVerifier.Helper.vb similarity index 100% rename from src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/CodeFixVerifier.Helper.vb rename to src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/CodeFixVerifier.Helper.vb diff --git a/src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticVerifier.Helper.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticVerifier.Helper.vb similarity index 100% rename from src/Templates/VS2015/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticVerifier.Helper.vb rename to src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/DiagnosticVerifier.Helper.vb diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Test.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Test.vstemplate new file mode 100644 index 000000000..504d400e2 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Test.vstemplate @@ -0,0 +1,43 @@ + + + + TestProject + <No description available> + + VisualBasic + 2.0 + 1000 + bb967cab-2ca5-4dac-8809-65b2b28a6351 + true + true + TestProject + true + true + + + + UnitTests.vb + + + Verifiers\CSAnalyzer.vb + Verifiers\CSAnalyzer+Test.vb + Verifiers\CSCodeFix.vb + Verifiers\CSCodeFix+Test.vb + Verifiers\CSRefactoring.vb + Verifiers\CSRefactoring+Test.vb + Verifiers\CSHelper.vb + Verifiers\VBAnalyzer.vb + Verifiers\VBAnalyzer+Test.vb + Verifiers\VBCodeFix.vb + Verifiers\VBCodeFix+Test.vb + Verifiers\VBRefactoring.vb + Verifiers\VBRefactoring+Test.vb + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKTestTemplateWizard + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/UnitTestProject.vbproj b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/UnitTestProject.vbproj new file mode 100644 index 000000000..570444648 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/UnitTestProject.vbproj @@ -0,0 +1,27 @@ + + + + netcoreapp2.0 + + true + true + + + + + + + + + + + + + + + + + + + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/UnitTests.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/UnitTests.vb new file mode 100644 index 000000000..3dd150769 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/UnitTests.vb @@ -0,0 +1,43 @@ +Imports Microsoft.VisualStudio.TestTools.UnitTesting +Imports VerifyVB = $safeprojectname$.VisualBasicCodeFixVerifier( + Of $saferootprojectname$.$saferootidentifiername$Analyzer, + $saferootprojectname$.$saferootidentifiername$CodeFixProvider) + +Namespace $safeprojectname$ + + Public Class $saferootidentifiername$UnitTest + + 'No diagnostics expected to show up + + Public Async Function TestMethod1() As Task + Dim test = "" + Await VerifyVB.VerifyAnalyzerAsync(test) + End Function + + 'Diagnostic And CodeFix both triggered And checked for + + Public Async Function TestMethod2() As Task + + Dim test = " +Class {|#0:TypeName|} + + Sub Main() + + End Sub + +End Class" + + Dim fixtest = " +Class TYPENAME + + Sub Main() + + End Sub + +End Class" + + Dim expected = VerifyVB.Diagnostic("$saferootidentifiername$").WithLocation(0).WithArguments("TypeName") + Await VerifyVB.VerifyCodeFixAsync(test, expected, fixtest) + End Function + End Class +End Namespace diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSAnalyzer+Test.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSAnalyzer+Test.vb new file mode 100644 index 000000000..d1d5facfd --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSAnalyzer+Test.vb @@ -0,0 +1,21 @@ +Imports Microsoft.CodeAnalysis.CSharp.Testing +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing.Verifiers + +Partial Public NotInheritable Class CSharpAnalyzerVerifier(Of TAnalyzer As {DiagnosticAnalyzer, New}) + Public Class Test + Inherits CSharpAnalyzerTest(Of TAnalyzer, MSTestVerifier) + + Public Sub New() + SolutionTransforms.Add( + Function(solution, projectId) + Dim compilationOptions = solution.GetProject(projectId).CompilationOptions + compilationOptions = compilationOptions.WithSpecificDiagnosticOptions( + compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)) + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions) + + Return solution + End Function) + End Sub + End Class +End Class diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSAnalyzer.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSAnalyzer.vb new file mode 100644 index 000000000..303122b9e --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSAnalyzer.vb @@ -0,0 +1,40 @@ +Imports System.Threading +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CSharp.Testing +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing +Imports Microsoft.CodeAnalysis.Testing.Verifiers + +Partial Public NotInheritable Class CSharpAnalyzerVerifier(Of TAnalyzer As {DiagnosticAnalyzer, New}) + + Private Sub New() + Throw New NotSupportedException() + End Sub + + ''' + Public Shared Function Diagnostic() As DiagnosticResult + Return CSharpAnalyzerVerifier(Of TAnalyzer, MSTestVerifier).Diagnostic() + End Function + + ''' + Public Shared Function Diagnostic(diagnosticId As String) As DiagnosticResult + Return CSharpAnalyzerVerifier(Of TAnalyzer, MSTestVerifier).Diagnostic(diagnosticId) + End Function + + ''' + Public Shared Function Diagnostic(descriptor As DiagnosticDescriptor) As DiagnosticResult + Return CSharpAnalyzerVerifier(Of TAnalyzer, MSTestVerifier).Diagnostic(descriptor) + End Function + + ''' + Public Shared Async Function VerifyAnalyzerAsync(source As String, ParamArray expected As DiagnosticResult()) As Task + Dim test As New Test With + { + .TestCode = source + } + + test.ExpectedDiagnostics.AddRange(expected) + Await test.RunAsync(CancellationToken.None) + End Function + +End Class diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSCodeFix+Test.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSCodeFix+Test.vb new file mode 100644 index 000000000..cf3f1df2d --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSCodeFix+Test.vb @@ -0,0 +1,22 @@ +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.CSharp.Testing +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing.Verifiers + +Partial Public NotInheritable Class CSharpCodeFixVerifier(Of TAnalyzer As {DiagnosticAnalyzer, New}, TCodeFix As {CodeFixProvider, New}) + Public Class Test + Inherits CSharpCodeFixTest(Of TAnalyzer, TCodeFix, MSTestVerifier) + + Public Sub New() + SolutionTransforms.Add( + Function(solution, projectId) + Dim compilationOptions = solution.GetProject(projectId).CompilationOptions + compilationOptions = compilationOptions.WithSpecificDiagnosticOptions( + compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)) + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions) + + Return solution + End Function) + End Sub + End Class +End Class diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSCodeFix.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSCodeFix.vb new file mode 100644 index 000000000..1433d62a5 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSCodeFix.vb @@ -0,0 +1,63 @@ +Imports System.Threading +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.CSharp.Testing +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing +Imports Microsoft.CodeAnalysis.Testing.Verifiers + +Partial Public NotInheritable Class CSharpCodeFixVerifier(Of TAnalyzer As {DiagnosticAnalyzer, New}, TCodeFix As {CodeFixProvider, New}) + + Private Sub New() + Throw New NotSupportedException() + End Sub + + ''' + Public Shared Function Diagnostic() As DiagnosticResult + Return CSharpCodeFixVerifier(Of TAnalyzer, TCodeFix, MSTestVerifier).Diagnostic() + End Function + + ''' + Public Shared Function Diagnostic(diagnosticId As String) As DiagnosticResult + Return CSharpCodeFixVerifier(Of TAnalyzer, TCodeFix, MSTestVerifier).Diagnostic(diagnosticId) + End Function + + ''' + Public Shared Function Diagnostic(descriptor As DiagnosticDescriptor) As DiagnosticResult + Return CSharpCodeFixVerifier(Of TAnalyzer, TCodeFix, MSTestVerifier).Diagnostic(descriptor) + End Function + + ''' + Public Shared Async Function VerifyAnalyzerAsync(source As String, ParamArray expected As DiagnosticResult()) As Task + Dim test As New Test With + { + .TestCode = source + } + + test.ExpectedDiagnostics.AddRange(expected) + Await test.RunAsync(CancellationToken.None) + End Function + + ''' + Public Shared Async Function VerifyCodeFixAsync(source As String, fixedSource As String) As Task + Await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource) + End Function + + ''' + Public Shared Async Function VerifyCodeFixAsync(source As String, expected As DiagnosticResult, fixedSource As String) As Task + Await VerifyCodeFixAsync(source, {expected}, fixedSource) + End Function + + ''' + Public Shared Async Function VerifyCodeFixAsync(source As String, expected As DiagnosticResult(), fixedSource As String) As Task + Dim test As New Test With + { + .TestCode = source, + .FixedCode = fixedSource + } + + test.ExpectedDiagnostics.AddRange(expected) + Await test.RunAsync(CancellationToken.None) + End Function + +End Class diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSHelper.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSHelper.vb new file mode 100644 index 000000000..0ec3d16dc --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSHelper.vb @@ -0,0 +1,27 @@ +Imports System.Collections.Immutable +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CSharp + +Friend Module CSharpVerifierHelper + ''' + ''' By default, the compiler reports diagnostics for nullable reference types at + ''' , and the analyzer test framework defaults to only validating + ''' diagnostics at . This map contains all compiler diagnostic IDs + ''' related to nullability mapped to , which is then used to enable all + ''' of these warnings for default validation during analyzer and code fix tests. + ''' + Friend ReadOnly Property NullableWarnings As ImmutableDictionary(Of String, ReportDiagnostic) = GetNullableWarningsFromCompiler() + + Private Function GetNullableWarningsFromCompiler() As ImmutableDictionary(Of String, ReportDiagnostic) + Dim args = {"/warnaserror:nullable"} + Dim commandLineArguments = CSharpCommandLineParser.Default.Parse(args, baseDirectory:=Environment.CurrentDirectory, sdkDirectory:=Environment.CurrentDirectory) + Dim nullableWarnings = commandLineArguments.CompilationOptions.SpecificDiagnosticOptions + + ' Workaround for https://github.com/dotnet/roslyn/issues/41610 + nullableWarnings = nullableWarnings _ + .SetItem("CS8632", ReportDiagnostic.Error) _ + .SetItem("CS8669", ReportDiagnostic.Error) + + Return nullableWarnings + End Function +End Module diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSRefactoring+Test.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSRefactoring+Test.vb new file mode 100644 index 000000000..be5dc91a4 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSRefactoring+Test.vb @@ -0,0 +1,21 @@ +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.CSharp.Testing +Imports Microsoft.CodeAnalysis.Testing.Verifiers + +Partial Public NotInheritable Class CSharpCodeRefactoringVerifier(Of TCodeRefactoring As {CodeRefactoringProvider, New}) + Public Class Test + Inherits CSharpCodeRefactoringTest(Of TCodeRefactoring, MSTestVerifier) + + Public Sub New() + SolutionTransforms.Add( + Function(solution, projectId) + Dim compilationOptions = solution.GetProject(projectId).CompilationOptions + compilationOptions = compilationOptions.WithSpecificDiagnosticOptions( + compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)) + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions) + + Return solution + End Function) + End Sub + End Class +End Class diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSRefactoring.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSRefactoring.vb new file mode 100644 index 000000000..8515a9fb4 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/CSRefactoring.vb @@ -0,0 +1,33 @@ +Imports System.Threading +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Testing + +Partial Public NotInheritable Class CSharpCodeRefactoringVerifier(Of TCodeRefactoring As {CodeRefactoringProvider, New}) + + Private Sub New() + Throw New NotSupportedException() + End Sub + + ''' + Public Shared Async Function VerifyRefactoringAsync(source As String, fixedSource As String) As Task + Await VerifyRefactoringAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource) + End Function + + ''' + Public Shared Async Function VerifyRefactoringAsync(source As String, expected As DiagnosticResult, fixedSource As String) As Task + Await VerifyRefactoringAsync(source, {expected}, fixedSource) + End Function + + ''' + Public Shared Async Function VerifyRefactoringAsync(source As String, expected As DiagnosticResult(), fixedSource As String) As Task + Dim test As New Test With + { + .TestCode = source, + .FixedCode = fixedSource + } + + test.ExpectedDiagnostics.AddRange(expected) + Await test.RunAsync(CancellationToken.None) + End Function + +End Class diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/VBAnalyzer+Test.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/VBAnalyzer+Test.vb new file mode 100644 index 000000000..277d74e43 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/VBAnalyzer+Test.vb @@ -0,0 +1,12 @@ +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing.Verifiers +Imports Microsoft.CodeAnalysis.VisualBasic.Testing + +Partial Public NotInheritable Class VisualBasicAnalyzerVerifier(Of TAnalyzer As {DiagnosticAnalyzer, New}) + Public Class Test + Inherits VisualBasicAnalyzerTest(Of TAnalyzer, MSTestVerifier) + + Public Sub New() + End Sub + End Class +End Class diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/VBAnalyzer.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/VBAnalyzer.vb new file mode 100644 index 000000000..2c97813d9 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/VBAnalyzer.vb @@ -0,0 +1,40 @@ +Imports System.Threading +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing +Imports Microsoft.CodeAnalysis.Testing.Verifiers +Imports Microsoft.CodeAnalysis.VisualBasic.Testing + +Partial Public NotInheritable Class VisualBasicAnalyzerVerifier(Of TAnalyzer As {DiagnosticAnalyzer, New}) + + Private Sub New() + Throw New NotSupportedException() + End Sub + + ''' + Public Shared Function Diagnostic() As DiagnosticResult + Return VisualBasicAnalyzerVerifier(Of TAnalyzer, MSTestVerifier).Diagnostic() + End Function + + ''' + Public Shared Function Diagnostic(diagnosticId As String) As DiagnosticResult + Return VisualBasicAnalyzerVerifier(Of TAnalyzer, MSTestVerifier).Diagnostic(diagnosticId) + End Function + + ''' + Public Shared Function Diagnostic(descriptor As DiagnosticDescriptor) As DiagnosticResult + Return VisualBasicAnalyzerVerifier(Of TAnalyzer, MSTestVerifier).Diagnostic(descriptor) + End Function + + ''' + Public Shared Async Function VerifyAnalyzerAsync(source As String, ParamArray expected As DiagnosticResult()) As Task + Dim test As New Test With + { + .TestCode = source + } + + test.ExpectedDiagnostics.AddRange(expected) + Await test.RunAsync(CancellationToken.None) + End Function + +End Class diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/VBCodeFix+Test.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/VBCodeFix+Test.vb new file mode 100644 index 000000000..9ffe19e4c --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/VBCodeFix+Test.vb @@ -0,0 +1,11 @@ +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing.Verifiers +Imports Microsoft.CodeAnalysis.VisualBasic.Testing + +Partial Public NotInheritable Class VisualBasicCodeFixVerifier(Of TAnalyzer As {DiagnosticAnalyzer, New}, TCodeFix As {CodeFixProvider, New}) + Public Class Test + Inherits VisualBasicCodeFixTest(Of TAnalyzer, TCodeFix, MSTestVerifier) + + End Class +End Class diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/VBCodeFix.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/VBCodeFix.vb new file mode 100644 index 000000000..36e546034 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/VBCodeFix.vb @@ -0,0 +1,63 @@ +Imports System.Threading +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Testing +Imports Microsoft.CodeAnalysis.Testing.Verifiers +Imports Microsoft.CodeAnalysis.VisualBasic.Testing + +Partial Public NotInheritable Class VisualBasicCodeFixVerifier(Of TAnalyzer As {DiagnosticAnalyzer, New}, TCodeFix As {CodeFixProvider, New}) + + Private Sub New() + Throw New NotSupportedException() + End Sub + + ''' + Public Shared Function Diagnostic() As DiagnosticResult + Return VisualBasicCodeFixVerifier(Of TAnalyzer, TCodeFix, MSTestVerifier).Diagnostic() + End Function + + ''' + Public Shared Function Diagnostic(diagnosticId As String) As DiagnosticResult + Return VisualBasicCodeFixVerifier(Of TAnalyzer, TCodeFix, MSTestVerifier).Diagnostic(diagnosticId) + End Function + + ''' + Public Shared Function Diagnostic(descriptor As DiagnosticDescriptor) As DiagnosticResult + Return VisualBasicCodeFixVerifier(Of TAnalyzer, TCodeFix, MSTestVerifier).Diagnostic(descriptor) + End Function + + ''' + Public Shared Async Function VerifyAnalyzerAsync(source As String, ParamArray expected As DiagnosticResult()) As Task + Dim test As New Test With + { + .TestCode = source + } + + test.ExpectedDiagnostics.AddRange(expected) + Await test.RunAsync(CancellationToken.None) + End Function + + ''' + Public Shared Async Function VerifyCodeFixAsync(source As String, fixedSource As String) As Task + Await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource) + End Function + + ''' + Public Shared Async Function VerifyCodeFixAsync(source As String, expected As DiagnosticResult, fixedSource As String) As Task + Await VerifyCodeFixAsync(source, {expected}, fixedSource) + End Function + + ''' + Public Shared Async Function VerifyCodeFixAsync(source As String, expected As DiagnosticResult(), fixedSource As String) As Task + Dim test As New Test With + { + .TestCode = source, + .FixedCode = fixedSource + } + + test.ExpectedDiagnostics.AddRange(expected) + Await test.RunAsync(CancellationToken.None) + End Function + +End Class diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/VBRefactoring+Test.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/VBRefactoring+Test.vb new file mode 100644 index 000000000..ce87585eb --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/VBRefactoring+Test.vb @@ -0,0 +1,10 @@ +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Testing.Verifiers +Imports Microsoft.CodeAnalysis.VisualBasic.Testing + +Partial Public NotInheritable Class VisualBasicCodeRefactoringVerifier(Of TCodeRefactoring As {CodeRefactoringProvider, New}) + Public Class Test + Inherits VisualBasicCodeRefactoringTest(Of TCodeRefactoring, MSTestVerifier) + + End Class +End Class diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/VBRefactoring.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/VBRefactoring.vb new file mode 100644 index 000000000..9a3b579b9 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Test/Verifiers/VBRefactoring.vb @@ -0,0 +1,33 @@ +Imports System.Threading +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Testing + +Partial Public NotInheritable Class VisualBasicCodeRefactoringVerifier(Of TCodeRefactoring As {CodeRefactoringProvider, New}) + + Private Sub New() + Throw New NotSupportedException() + End Sub + + ''' + Public Shared Async Function VerifyRefactoringAsync(source As String, fixedSource As String) As Task + Await VerifyRefactoringAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource) + End Function + + ''' + Public Shared Async Function VerifyRefactoringAsync(source As String, expected As DiagnosticResult, fixedSource As String) As Task + Await VerifyRefactoringAsync(source, {expected}, fixedSource) + End Function + + ''' + Public Shared Async Function VerifyRefactoringAsync(source As String, expected As DiagnosticResult(), fixedSource As String) As Task + Dim test As New Test With + { + .TestCode = source, + .FixedCode = fixedSource + } + + test.ExpectedDiagnostics.AddRange(expected) + Await test.RunAsync(CancellationToken.None) + End Function + +End Class diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Vsix/Deployment.vbproj b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Vsix/Deployment.vbproj new file mode 100644 index 000000000..7826f5d71 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Vsix/Deployment.vbproj @@ -0,0 +1,43 @@ + + + + + + net472 + $saferootprojectname$.Vsix + $saferootprojectname$.Vsix + + + + false + false + false + false + false + false + Roslyn + + + + + + + + Program + $(DevEnvDir)devenv.exe + /rootsuffix $(VSSDKTargetPlatformRegRootSuffix) + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Vsix/Deployment.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Vsix/Deployment.vstemplate new file mode 100644 index 000000000..bb1c0355e --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Vsix/Deployment.vstemplate @@ -0,0 +1,28 @@ + + + + Vsix + <No description available> + + VisualBasic + 2.0 + 1000 + bb967cab-2ca5-4dac-8809-65b2b28a6350 + true + true + Vsix + true + true + + + + source.extension.vsixmanifest + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKVsixTemplateWizardThirdProject + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Vsix/source.extension.vsixmanifest b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Vsix/source.extension.vsixmanifest new file mode 100644 index 000000000..b5d1aa844 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Vsix/source.extension.vsixmanifest @@ -0,0 +1,24 @@ + + + + + $saferootprojectname$ + This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn"). + + + + + + + + + + + + + + + + + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/Properties/AssemblyInfo.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..710a6f0cb --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.VisualStudio.Shell; + +[assembly: ProvideBindingRedirection(CodeBase = "Roslyn.SDK.Template.Wizard.dll", OldVersionLowerBound = "1.0.0.0")] +[assembly: ProvideCodeBase(AssemblyName = "Roslyn.ComponentDebugger", CodeBase = "$PackageFolder$\\Roslyn.ComponentDebugger.dll")] diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/Properties/launchSettings.json b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/Properties/launchSettings.json new file mode 100644 index 000000000..a46dc995f --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "profiles": { + "Templates.VisualStudio.2017": { + "commandName": "Executable", + "executablePath": "$(DevEnvDir)devenv.exe", + "commandLineArgs": "/rootsuffix RoslynDev /log" + } + } +} \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/Roslyn.SDK.csproj b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/Roslyn.SDK.csproj new file mode 100644 index 000000000..8f82ad750 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/Roslyn.SDK.csproj @@ -0,0 +1,138 @@ + + + + net472 + Roslyn.SDK + Roslyn.SDK + false + true + false + false + + Microsoft.CodeAnalysis.SDK + Neutral + false + + + + + + + + + + + + + + + Designer + Extensibility + + + Designer + Extensibility + + + Designer + Extensibility + + + + + + Designer + Extensibility + + + Designer + Extensibility + + + Designer + Extensibility + + + + + + Designer + Extensibility + + + Designer + Extensibility + + + Designer + Extensibility + + + + + + Designer + Extensibility + + + Designer + Extensibility + + + Designer + Extensibility + + + + + + SyntaxTree.bmp + true + + + SyntaxTree.ico + true + + + ThirdPartyNotices.rtf + true + + + EULA.rtf + true + + + + + + Roslyn.SDK.Template.Wizard + false + + + false + + + false + + + Roslyn.SyntaxVisualizer.Extension + BuiltProjectOutputGroup%3bGetCopyToOutputDirectoryItems%3bPkgdefProjectOutputGroup + DebugSymbolsProjectOutputGroup%3b + true + false + + + Roslyn.ComponentDebugger + BuiltProjectOutputGroup%3bGetCopyToOutputDirectoryItems + DebugSymbolsProjectOutputGroup + true + false + + + + + + + + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/VBDiagnostic.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/VBDiagnostic.vstemplate new file mode 100644 index 000000000..bf6f5d9a6 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/VBDiagnostic.vstemplate @@ -0,0 +1,52 @@ + + + + Analyzer with Code Fix (.NET Standard) + Create a Visual Basic analyzer that comes with a code fix and generates diagnostics. The analyzer can be deployed as either a NuGet package or a VSIX extension. + CodeInformation.ico + VisualBasic + 2.0 + 1000 + Microsoft.VisualBasic.Analyzer + true + true + Analyzer + true + true + Visual Basic + Windows + Linux + macOS + VSSDK + Roslyn + Extensions + + + + + + ProjectTemplates\VisualBasic\Diagnostic\Analyzer\DiagnosticAnalyzer.vstemplate + + + ProjectTemplates\VisualBasic\Diagnostic\CodeFix\CodeFixProvider.vstemplate + + + ProjectTemplates\VisualBasic\Diagnostic\Package\Package.vstemplate + + + ProjectTemplates\VisualBasic\Diagnostic\Test\Test.vstemplate + + + ProjectTemplates\VisualBasic\Diagnostic\Vsix\Deployment.vstemplate + + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKRootTemplateWizard + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/VBRef.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/VBRef.vstemplate new file mode 100644 index 000000000..191b18f62 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/VBRef.vstemplate @@ -0,0 +1,43 @@ + + + + Code Refactoring (.NET Standard) + Create a Visual Basic refactoring, deployed as a VSIX extension + CodeInformation.ico + VisualBasic + 2.0 + 1000 + Microsoft.VisualBasic.CodeRefactoring + true + true + CodeRefactoring + true + true + Visual Basic + Windows + Linux + macOS + VSSDK + Roslyn + Extensions + + + + + + ProjectTemplates\VisualBasic\CodeRefactoring\Ref\VBCodeRefactoring.vstemplate + + + ProjectTemplates\VisualBasic\CodeRefactoring\Vsix\Deployment.vstemplate + + + + + Roslyn.SDK.Template.Wizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 + RoslynSDKRootTemplateWizard + + \ No newline at end of file diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/source.extension.vsixmanifest b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/source.extension.vsixmanifest new file mode 100644 index 000000000..a091d5313 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/source.extension.vsixmanifest @@ -0,0 +1,41 @@ + + + + + .NET Compiler Platform SDK For Visual Studio 2017 + The .NET Compiler Platform ("Roslyn") provides open-source C# and Visual Basic compilers with rich code analysis APIs. You can build code analysis tools with the same APIs that Microsoft is using to implement Visual Studio! Also includes the Syntax Visualizer, a Visual Studio extension that allows you to inspect and explore the syntax trees you'll use as you build applications and VS extensions atop the .NET Compiler Platform ("Roslyn"). + Roslyn.SDK + EULA.rtf + roslyn,template,SDK + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/ColorHelpers.cs b/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/ColorHelpers.cs new file mode 100644 index 000000000..e7540955a --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/ColorHelpers.cs @@ -0,0 +1,39 @@ +using System; +using System.Windows.Media; + +namespace Roslyn.SyntaxVisualizer.Control +{ + internal static class ColorHelpers + { + /// + /// From https://en.wikipedia.org/wiki/HSL_and_HSV + /// + internal static Color HSVToColor(double hue, double saturation, double value) + { + hue = Clamp(hue, 0, 360); + saturation = Clamp(saturation, 0, 1); + value = Clamp(value, 0, 1); + + byte f(double d) + { + var k = (d + hue / 60) % 6; + var v = value - value * saturation * Math.Max(Math.Min(Math.Min(k, 4 - k), 1), 0); + return (byte)Math.Round(v * 255); + } + + return Color.FromRgb(f(5), f(3), f(1)); + } + + internal static double GetHue(Color color) + => System.Drawing.Color.FromArgb(color.A, color.R, color.G, color.B).GetHue(); + + internal static double GetBrightness(Color color) + => System.Drawing.Color.FromArgb(color.A, color.R, color.G, color.B).GetBrightness(); + + internal static double GetSaturation(Color color) + => System.Drawing.Color.FromArgb(color.A, color.R, color.G, color.B).GetSaturation(); + + private static double Clamp(double value, double min, double max) + => Math.Max(min, Math.Min(value, max)); + } +} diff --git a/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/ColorPicker.xaml b/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/ColorPicker.xaml new file mode 100644 index 000000000..fa2368a03 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/ColorPicker.xaml @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/ColorPicker.xaml.cs b/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/ColorPicker.xaml.cs new file mode 100644 index 000000000..7b126a72a --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/ColorPicker.xaml.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +namespace Roslyn.SyntaxVisualizer.Control +{ + /// + /// Interaction logic for ColorPicker.xaml + /// + public partial class ColorPicker : UserControl + { + public static readonly DependencyProperty ColorProperty = DependencyProperty.Register( + nameof(Color), + typeof(Color), + typeof(ColorPicker), + new PropertyMetadata(Colors.Red, OnColorChanged)); + + public static readonly DependencyProperty OriginalColorProperty = DependencyProperty.Register( + nameof(OriginalColor), + typeof(Color), + typeof(ColorPicker), + new PropertyMetadata(Colors.Red, OnOriginalColorChanged)); + + public ColorPicker() + { + var vm = new ColorPickerViewModel(); + DataContext = vm; + InitializeComponent(); + + vm.PropertyChanged += (s, a) => + { + switch (a.PropertyName) + { + case nameof(ColorPickerViewModel.Color): + Color = vm.Color; + break; + + case nameof(ColorPickerViewModel.OriginalColor): + OriginalColor = vm.OriginalColor; + break; + } + }; + } + + public Color Color + { + get => (Color)GetValue(ColorProperty); + set => SetValue(ColorProperty, value); + } + + public Color OriginalColor + { + get => (Color)GetValue(OriginalColorProperty); + set => SetValue(OriginalColorProperty, value); + } + + private void SelectAllText(object sender, RoutedEventArgs e) + { + if (sender is TextBox textBox) + { + textBox.SelectAll(); + } + } + + private static void OnColorChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + var colorPicker = (ColorPicker)o; + var vm = (ColorPickerViewModel)colorPicker.DataContext; + vm.Color = (Color)e.NewValue; + } + + private static void OnOriginalColorChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + var colorPicker = (ColorPicker)o; + var vm = (ColorPickerViewModel)colorPicker.DataContext; + vm.OriginalColor = (Color)e.NewValue; + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/ColorPickerViewModel.cs b/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/ColorPickerViewModel.cs new file mode 100644 index 000000000..269184d4e --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/ColorPickerViewModel.cs @@ -0,0 +1,218 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Windows.Media; + +namespace Roslyn.SyntaxVisualizer.Control +{ + internal class ColorPickerViewModel : INotifyPropertyChanged + { + private UpdateBehavior _updateBehavior = UpdateBehavior.None; + public ColorPickerViewModel() + { + } + + private Color _originalColor; + public Color OriginalColor + { + get => _originalColor; + set => SetProperty(ref _originalColor, value); + } + + private Color _color; + public Color Color + { + get => _color; + set + { + if (!SetProperty(ref _color, value)) + { + return; + } + + if (_updateBehavior != UpdateBehavior.FromComponent) + { + UpdateColorComponents(); + } + } + } + + private double _hue; + public double Hue + { + get => _hue; + set + { + if (!SetProperty(ref _hue, value)) + { + return; + } + + if (_updateBehavior == UpdateBehavior.None) + { + UpdateColorFromHSB(); + } + } + } + + private double _saturation; + public double Saturation + { + get => _saturation; + set + { + if (!SetProperty(ref _saturation, value)) + { + return; + } + + if (_updateBehavior == UpdateBehavior.None) + { + UpdateColorFromHSB(); + } + } + } + + private double _brightness; + public double Brightness + { + get => _brightness; + set + { + if (!SetProperty(ref _brightness, value)) + { + return; + } + + if (_updateBehavior == UpdateBehavior.None) + { + UpdateColorFromHSB(); + } + } + } + + private byte _red; + public byte Red + { + get => _red; + set + { + if (!SetProperty(ref _red, value)) + { + return; + } + + if (_updateBehavior == UpdateBehavior.None) + { + UpdateColorFromRGB(); + } + } + } + + private byte _green; + public byte Green + { + get => _green; + set + { + if (!SetProperty(ref _green, value)) + { + return; + } + + if (_updateBehavior == UpdateBehavior.None) + { + UpdateColorFromRGB(); + } + } + } + + private byte _blue; + public byte Blue + { + get => _blue; + set + { + if (!SetProperty(ref _blue, value)) + { + return; + } + + if (_updateBehavior == UpdateBehavior.None) + { + UpdateColorFromRGB(); + } + } + } + + private void UpdateColorFromRGB() + { + _updateBehavior = UpdateBehavior.FromComponent; + + Color = Color.FromRgb(Red, Green, Blue); + Hue = ColorHelpers.GetHue(Color); + Saturation = ColorHelpers.GetSaturation(Color); + Brightness = ColorHelpers.GetSaturation(Color); + + _updateBehavior = UpdateBehavior.None; + } + + private void UpdateColorFromHSB() + { + _updateBehavior = UpdateBehavior.FromComponent; + + Color = ColorHelpers.HSVToColor(Hue, Saturation, Brightness); + Red = Color.R; + Green = Color.G; + Blue = Color.B; + + _updateBehavior = UpdateBehavior.None; + } + + private void UpdateColorComponents() + { + _updateBehavior = UpdateBehavior.FromColor; + + Red = Color.R; + Green = Color.G; + Blue = Color.B; + Hue = ColorHelpers.GetHue(Color); + Saturation = ColorHelpers.GetSaturation(Color); + Brightness = ColorHelpers.GetSaturation(Color); + + _updateBehavior = UpdateBehavior.None; + } + + #region NotifyPropertyChanged + public event PropertyChangedEventHandler? PropertyChanged; + private bool SetProperty(ref T backingField, T value, [CallerMemberName] string propertyName = "") + { + if (EqualityComparer.Default.Equals(backingField, value)) + { + return false; + } + + backingField = value; + NotifyPropertyChanged(propertyName); + return true; + } + + private void NotifyPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + #endregion + + private enum UpdateBehavior + { + None, + + /// + /// Update is triggered from the Color property being updated + /// + FromColor, + + /// + /// Update is triggered from a component of Color, such as Hue + /// + FromComponent + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/ColorPickerWindow.xaml b/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/ColorPickerWindow.xaml new file mode 100644 index 000000000..15779b538 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/ColorPickerWindow.xaml @@ -0,0 +1,37 @@ + + + + + + + + + + + + +