diff --git a/Roslyn-SDK.sln b/Roslyn-SDK.sln index 5924613d9d..a1bc3103d1 100644 --- a/Roslyn-SDK.sln +++ b/Roslyn-SDK.sln @@ -149,6 +149,8 @@ Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.Visu 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 Global @@ -421,6 +423,10 @@ Global {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 @@ -501,6 +507,7 @@ Global {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} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/eng/Versions.props b/eng/Versions.props index 9861332323..a531698656 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -33,11 +33,15 @@ 16.4.280 7.10.6071 16.5.29911.84 + 16.5.29911.84 7.10.6072 8.0.50728 9.0.30730 10.0.30320 11.0.61031 + 12.1.30329 + 14.3.26929 + 15.7.1 16.4.280 16.4.280 7.10.6071 @@ -45,6 +49,7 @@ 16.4.280 16.5.29903.186 16.5.132 + 5.6.0 2.3.99 1.3.1 @@ -55,6 +60,7 @@ 2.6.1 3.3.1 1.2.7 + 0.1.49-beta 2.9.8 1.2.0-beta.164 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 0000000000..057f0ad344 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKCodeFixTemplateWizard.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 EnvDTE; +using VSLangProj; + +public class RoslynSDKCodeFixTemplateWizard : RoslynSDKChildTemplateWizard +{ + public static Project Project { get; private set; } + + public override void OnProjectFinishedGenerating(Project project) + { + Project = project; + + // There is no good way for the test project to reference the main project, so we will use the wizard. +#pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread + if (project.Object is VSProject vsProject) +#pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread + { + var referenceProject = 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 0000000000..6d3815b6ba --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKPackageTemplateWizard.cs @@ -0,0 +1,19 @@ +// 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 VSLangProj; + +public class RoslynSDKPackageTemplateWizard : 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. +#pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread + if (project.Object is VSProject vsProject) +#pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread + { + _ = vsProject.References.AddProject(RoslynSDKAnalyzerTemplateWizard.Project); + _ = vsProject.References.AddProject(RoslynSDKCodeFixTemplateWizard.Project); + } + } +} diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKTestTemplateWizard.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKTestTemplateWizard.cs index 3ae6353978..821b331015 100644 --- a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKTestTemplateWizard.cs +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK.Template.Wizard/RoslynSDKTestTemplateWizard.cs @@ -12,7 +12,8 @@ public override void OnProjectFinishedGenerating(Project project) if (project.Object is VSProject vsProject) #pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread { - var referenceProject = vsProject.References.AddProject(RoslynSDKAnalyzerTemplateWizard.Project); + _ = vsProject.References.AddProject(RoslynSDKAnalyzerTemplateWizard.Project); + _ = vsProject.References.AddProject(RoslynSDKCodeFixTemplateWizard.Project); } } } diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/CSharpDiagnostic.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/CSharpDiagnostic.vstemplate index 33882df70c..9cbb893ee5 100644 --- a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/CSharpDiagnostic.vstemplate +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/CSharpDiagnostic.vstemplate @@ -29,7 +29,13 @@ Modifying it requires changes to the wizard code.--> - ProjectTemplates\CSharp\Diagnostic\Analyzer\DiagnosticAnalyzer.vstemplate + ProjectTemplates\CSharp\Diagnostic\Analyzer\DiagnosticAnalyzer.vstemplate + + + ProjectTemplates\CSharp\Diagnostic\CodeFix\CodeFixProvider.vstemplate + + + ProjectTemplates\CSharp\Diagnostic\Package\Package.vstemplate ProjectTemplates\CSharp\Diagnostic\Test\Test.vstemplate 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 index 8e93db2978..b50f469c83 100644 --- 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 @@ -2,30 +2,15 @@ netstandard2.0 - false - true - True - + false - - $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 + + *$(MSBuildProjectFullPath)* - + @@ -33,9 +18,4 @@ - - - - - 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 index 47b9f22045..6a305f4e96 100644 --- 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 @@ -19,11 +19,8 @@ DiagnosticAnalyzer.cs - CodeFixProvider.cs Resources.resx Resources.Designer.cs - install.ps1 - uninstall.ps1 diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/CodeFixProvider.cs b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/CodeFix/CodeFixProvider.cs similarity index 95% rename from src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/CodeFixProvider.cs rename to src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/CodeFix/CodeFixProvider.cs index 6380fb5929..cee0d31814 100644 --- a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/CodeFixProvider.cs +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/CodeFix/CodeFixProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; @@ -18,8 +18,6 @@ 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); } @@ -45,9 +43,9 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) // Register a code action that will invoke the fix. context.RegisterCodeFix( CodeAction.Create( - title: title, + title: CodeFixResources.CodeFixTitle, createChangedSolution: c => MakeUppercaseAsync(context.Document, declaration, c), - equivalenceKey: title), + equivalenceKey: nameof(CodeFixResources.CodeFixTitle)), diagnostic); } 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 0000000000..1c091606ec --- /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 0000000000..267bf2505a --- /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 0000000000..07e319a98e --- /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 0000000000..97abe68945 --- /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 0000000000..b37b009b41 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Package/Package.csproj @@ -0,0 +1,39 @@ + + + + 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 + + $(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 0000000000..97094dcb69 --- /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/Analyzer/install.ps1 b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Package/install.ps1 similarity index 100% rename from src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/install.ps1 rename to src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Package/install.ps1 diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/uninstall.ps1 b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Package/uninstall.ps1 similarity index 100% rename from src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Analyzer/uninstall.ps1 rename to src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/CSharp/Diagnostic/Package/uninstall.ps1 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 index 71b6305026..5412f6779f 100644 --- 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 @@ -14,6 +14,8 @@ + + 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 index d71b112969..d151c678fb 100644 --- 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 @@ -2,40 +2,20 @@ netstandard2.0 - false - true - True - + false - - $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 + + *$(MSBuildProjectFullPath)* - - - - - - + - - + + 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 index 824440cdfe..632f6de039 100644 --- 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 @@ -19,11 +19,8 @@ DiagnosticAnalyzer.vb - CodeFixProvider.vb Resources.resx Resources.Designer.vb - install.ps1 - uninstall.ps1 diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/CodeFixProvider.vb b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/CodeFix/CodeFixProvider.vb similarity index 96% rename from src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/CodeFixProvider.vb rename to src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/CodeFix/CodeFixProvider.vb index d7809633cd..0c722c5667 100644 --- a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/CodeFixProvider.vb +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/CodeFix/CodeFixProvider.vb @@ -17,8 +17,6 @@ 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) @@ -44,9 +42,9 @@ Public Class $saferootidentifiername$CodeFixProvider ' Register a code action that will invoke the fix. context.RegisterCodeFix( CodeAction.Create( - title:=title, + title:=My.Resources.CodeFixTitle, createChangedSolution:=Function(c) MakeUppercaseAsync(context.Document, declaration, c), - equivalenceKey:=title), + equivalenceKey:=NameOf(My.Resources.CodeFixTitle)), diagnostic) End Function 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 0000000000..448fff5398 --- /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 0000000000..113c0562c3 --- /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 0000000000..ea770a00dc --- /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 0000000000..d7172bdc70 --- /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 0000000000..86005e4554 --- /dev/null +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Package/Package.vbproj @@ -0,0 +1,39 @@ + + + + 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 + + $(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 0000000000..fdf0615503 --- /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/Analyzer/install.ps1 b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Package/install.ps1 similarity index 100% rename from src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/install.ps1 rename to src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Package/install.ps1 diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/uninstall.ps1 b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Package/uninstall.ps1 similarity index 100% rename from src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Analyzer/uninstall.ps1 rename to src/VisualStudio.Roslyn.SDK/Roslyn.SDK/ProjectTemplates/VisualBasic/Diagnostic/Package/uninstall.ps1 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 index 3ad382c546..b5d1aa8449 100644 --- 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 @@ -14,6 +14,8 @@ + + diff --git a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/VBDiagnostic.vstemplate b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/VBDiagnostic.vstemplate index afee41846a..bf6f5d9a63 100644 --- a/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/VBDiagnostic.vstemplate +++ b/src/VisualStudio.Roslyn.SDK/Roslyn.SDK/VBDiagnostic.vstemplate @@ -31,6 +31,12 @@ ProjectTemplates\VisualBasic\Diagnostic\Analyzer\DiagnosticAnalyzer.vstemplate + + ProjectTemplates\VisualBasic\Diagnostic\CodeFix\CodeFixProvider.vstemplate + + + ProjectTemplates\VisualBasic\Diagnostic\Package\Package.vstemplate + ProjectTemplates\VisualBasic\Diagnostic\Test\Test.vstemplate diff --git a/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/AbstractIdeIntegrationTest.cs b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/AbstractIdeIntegrationTest.cs new file mode 100644 index 0000000000..d9cea4125d --- /dev/null +++ b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/AbstractIdeIntegrationTest.cs @@ -0,0 +1,116 @@ +// 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.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Threading; +using Microsoft.CodeAnalysis.Testing.InProcess; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; +using Xunit; +using Task = System.Threading.Tasks.Task; + +namespace Microsoft.CodeAnalysis.Testing +{ + [VsTestSettings(UIThread = true, Version = "2019")] + public abstract class AbstractIdeIntegrationTest : IAsyncLifetime, IDisposable + { + /// + /// A long timeout used to avoid hangs in tests, where a test failure manifests as an operation never occurring. + /// + public static readonly TimeSpan HangMitigatingTimeout = TimeSpan.FromMinutes(1); + + private JoinableTaskContext? _joinableTaskContext; + private JoinableTaskCollection? _joinableTaskCollection; + private JoinableTaskFactory? _joinableTaskFactory; + + private TestServices? _testServices; + + private CancellationTokenSource _hangMitigatingCancellationTokenSource; + + protected AbstractIdeIntegrationTest() + { + Assert.True(Application.Current.Dispatcher.CheckAccess()); + + JoinableTaskContext = ThreadHelper.JoinableTaskContext; + + _hangMitigatingCancellationTokenSource = new CancellationTokenSource(HangMitigatingTimeout); + } + + [NotNull] + protected JoinableTaskContext? JoinableTaskContext + { + get + { + return _joinableTaskContext ?? throw new InvalidOperationException(); + } + + private set + { + if (value == _joinableTaskContext) + { + return; + } + + if (value is null) + { + _joinableTaskContext = null; + _joinableTaskCollection = null; + _joinableTaskFactory = null; + } + else + { + _joinableTaskContext = value; + _joinableTaskCollection = value.CreateCollection(); + _joinableTaskFactory = value.CreateFactory(_joinableTaskCollection).WithPriority(Application.Current.Dispatcher, DispatcherPriority.Background); + } + } + } + + [NotNull] + protected TestServices? TestServices + { + get + { + return _testServices ?? throw new InvalidOperationException(); + } + + private set + { + _testServices = value; + } + } + + protected JoinableTaskFactory JoinableTaskFactory + => _joinableTaskFactory ?? throw new InvalidOperationException(); + + protected CancellationToken HangMitigatingCancellationToken + => _hangMitigatingCancellationTokenSource.Token; + + public virtual async Task InitializeAsync() + { + TestServices = await CreateTestServicesAsync(); + } + + public virtual async Task DisposeAsync() + { + if (_joinableTaskCollection is object) + { + await _joinableTaskCollection.JoinTillEmptyAsync(); + } + + JoinableTaskContext = null; + } + + public virtual void Dispose() + { + } + + protected virtual async Task CreateTestServicesAsync() + => await TestServices.CreateAsync(JoinableTaskFactory); + } +} diff --git a/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/CreateProjectTests.cs b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/CreateProjectTests.cs new file mode 100644 index 0000000000..cd3e0b6ee8 --- /dev/null +++ b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/CreateProjectTests.cs @@ -0,0 +1,158 @@ +// 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.Testing.Verifiers; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Threading; +using Xunit; +using Task = System.Threading.Tasks.Task; + +namespace Microsoft.CodeAnalysis.Testing +{ + public class CreateProjectTests : AbstractIdeIntegrationTest + { + public override async Task DisposeAsync() + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + await TestServices.SolutionExplorer.CloseSolutionAsync(); + + await base.DisposeAsync(); + } + + [VsFact] + public async Task CreateFromTemplateAsync() + { + await TestServices.SolutionExplorer.CreateSolutionAsync(nameof(CreateProjectTests)); + await TestServices.SolutionExplorer.AddProjectAsync("TestProj", WellKnownProjectTemplates.ClassLibrary, languageName: LanguageNames.CSharp); + await TestServices.SolutionExplorer.RestoreNuGetPackagesAsync(HangMitigatingCancellationToken); + + await TestServices.Editor.SetTextAsync(@"using System"); + + var buildSummary = await TestServices.SolutionExplorer.BuildSolutionAsync(waitForBuildToFinish: true); + Assert.Equal("========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========", buildSummary); + + await TestServices.ErrorList.ShowBuildErrorsAsync(); + + // Verify that intentional errors get validated by the test + var errors = await TestServices.ErrorList.GetBuildErrorsAsync(__VSERRORCATEGORY.EC_ERROR); + var expected = "(Compiler) Class1.cs(1, 13): error CS1002: ; expected"; + new XUnitVerifier().EqualOrDiff(expected, string.Join(Environment.NewLine, errors)); + Assert.Equal(1, await TestServices.ErrorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_ERROR)); + } + + [VsFact] + public async Task CreateAnalyzerFromCSharpTemplateAsync() + { + await TestServices.SolutionExplorer.CreateSolutionAsync(nameof(CreateProjectTests)); + await TestServices.SolutionExplorer.AddProjectAsync("TestProj", "Microsoft.CSharp.Analyzer", languageName: LanguageNames.CSharp); + await TestServices.SolutionExplorer.RestoreNuGetPackagesAsync(HangMitigatingCancellationToken); + + var buildSummary = await TestServices.SolutionExplorer.BuildSolutionAsync(waitForBuildToFinish: true); + Assert.Equal("========== Build: 5 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========", buildSummary); + + await TestServices.ErrorList.ShowBuildErrorsAsync(); + + var errors = await TestServices.ErrorList.GetBuildErrorsAsync(__VSERRORCATEGORY.EC_ERROR); + new XUnitVerifier().EqualOrDiff(string.Empty, string.Join(Environment.NewLine, errors)); + Assert.Equal(0, await TestServices.ErrorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_ERROR)); + + // Currently have two analyzer warnings in the template. + var warnings = await TestServices.ErrorList.GetBuildErrorsAsync(__VSERRORCATEGORY.EC_WARNING); + var expected = @"(Compiler) TestProjAnalyzer.cs(29, 57): warning RS1025: Configure generated code analysis +(Compiler) TestProjAnalyzer.cs(29, 57): warning RS1026: Enable concurrent execution"; + new XUnitVerifier().EqualOrDiff(expected, string.Join(Environment.NewLine, warnings)); + Assert.Equal(2, await TestServices.ErrorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_WARNING)); + } + + [VsFact] + public async Task CreateRefactoringFromCSharpTemplateAsync() + { + await TestServices.SolutionExplorer.CreateSolutionAsync(nameof(CreateProjectTests)); + await TestServices.SolutionExplorer.AddProjectAsync("TestProj", "Microsoft.CSharp.CodeRefactoring", languageName: LanguageNames.CSharp); + await TestServices.SolutionExplorer.RestoreNuGetPackagesAsync(HangMitigatingCancellationToken); + + var buildSummary = await TestServices.SolutionExplorer.BuildSolutionAsync(waitForBuildToFinish: true); + Assert.Equal("========== Build: 2 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========", buildSummary); + + await TestServices.ErrorList.ShowBuildErrorsAsync(); + + Assert.Equal(0, await TestServices.ErrorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_ERROR)); + Assert.Equal(0, await TestServices.ErrorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_WARNING)); + } + + [VsFact] + public async Task CreateStandaloneToolFromCSharpTemplateAsync() + { + await TestServices.SolutionExplorer.CreateSolutionAsync(nameof(CreateProjectTests)); + await TestServices.SolutionExplorer.AddProjectAsync("TestProj", "Microsoft.CSharp.StandaloneCodeAnalysis", languageName: LanguageNames.CSharp); + await TestServices.SolutionExplorer.RestoreNuGetPackagesAsync(HangMitigatingCancellationToken); + + var buildSummary = await TestServices.SolutionExplorer.BuildSolutionAsync(waitForBuildToFinish: true); + Assert.Equal("========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========", buildSummary); + + await TestServices.ErrorList.ShowBuildErrorsAsync(); + + Assert.Equal(0, await TestServices.ErrorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_ERROR)); + Assert.Equal(0, await TestServices.ErrorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_WARNING)); + } + + [VsFact] + public async Task CreateAnalyzerFromVisualBasicTemplateAsync() + { + await TestServices.SolutionExplorer.CreateSolutionAsync(nameof(CreateProjectTests)); + await TestServices.SolutionExplorer.AddProjectAsync("TestProj", "Microsoft.VisualBasic.Analyzer", languageName: LanguageNames.VisualBasic); + await TestServices.SolutionExplorer.RestoreNuGetPackagesAsync(HangMitigatingCancellationToken); + + var buildSummary = await TestServices.SolutionExplorer.BuildSolutionAsync(waitForBuildToFinish: true); + Assert.Equal("========== Build: 5 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========", buildSummary); + + await TestServices.ErrorList.ShowBuildErrorsAsync(); + + var errors = await TestServices.ErrorList.GetBuildErrorsAsync(__VSERRORCATEGORY.EC_ERROR); + new XUnitVerifier().EqualOrDiff(string.Empty, string.Join(Environment.NewLine, errors)); + Assert.Equal(0, await TestServices.ErrorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_ERROR)); + + // Currently have two analyzer warnings in the template. + var warnings = await TestServices.ErrorList.GetBuildErrorsAsync(__VSERRORCATEGORY.EC_WARNING); + var expected = @"(Compiler) TestProjAnalyzer.vb(32, 37): warning RS1025: Configure generated code analysis +(Compiler) TestProjAnalyzer.vb(32, 37): warning RS1026: Enable concurrent execution"; + new XUnitVerifier().EqualOrDiff(expected, string.Join(Environment.NewLine, warnings)); + Assert.Equal(2, await TestServices.ErrorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_WARNING)); + } + + [VsFact] + public async Task CreateRefactoringFromVisualBasicTemplateAsync() + { + await TestServices.SolutionExplorer.CreateSolutionAsync(nameof(CreateProjectTests)); + await TestServices.SolutionExplorer.AddProjectAsync("TestProj", "Microsoft.VisualBasic.CodeRefactoring", languageName: LanguageNames.VisualBasic); + await TestServices.SolutionExplorer.RestoreNuGetPackagesAsync(HangMitigatingCancellationToken); + + var buildSummary = await TestServices.SolutionExplorer.BuildSolutionAsync(waitForBuildToFinish: true); + Assert.Equal("========== Build: 2 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========", buildSummary); + + await TestServices.ErrorList.ShowBuildErrorsAsync(); + + Assert.Equal(0, await TestServices.ErrorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_ERROR)); + Assert.Equal(0, await TestServices.ErrorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_WARNING)); + } + + [VsFact] + public async Task CreateStandaloneToolFromVisualBasicTemplateAsync() + { + await TestServices.SolutionExplorer.CreateSolutionAsync(nameof(CreateProjectTests)); + await TestServices.SolutionExplorer.AddProjectAsync("TestProj", "Microsoft.VisualBasic.StandaloneCodeAnalysis", languageName: LanguageNames.VisualBasic); + await TestServices.SolutionExplorer.RestoreNuGetPackagesAsync(HangMitigatingCancellationToken); + + var buildSummary = await TestServices.SolutionExplorer.BuildSolutionAsync(waitForBuildToFinish: true); + Assert.Equal("========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========", buildSummary); + + await TestServices.ErrorList.ShowBuildErrorsAsync(); + + Assert.Equal(0, await TestServices.ErrorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_ERROR)); + Assert.Equal(0, await TestServices.ErrorList.GetErrorCountAsync(__VSERRORCATEGORY.EC_WARNING)); + } + } +} diff --git a/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/EditorInProcess.cs b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/EditorInProcess.cs new file mode 100644 index 0000000000..da5a38bde2 --- /dev/null +++ b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/EditorInProcess.cs @@ -0,0 +1,64 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.TextManager.Interop; + +namespace Microsoft.CodeAnalysis.Testing.InProcess +{ + public class EditorInProcess : InProcComponent + { + internal static readonly Guid IWpfTextViewId = new Guid("8C40265E-9FDB-4F54-A0FD-EBB72B7D0476"); + + public EditorInProcess(TestServices testServices) + : base(testServices) + { + } + + public async Task GetActiveTextViewAsync() + => (await GetActiveTextViewHostAsync()).TextView; + + private async Task GetActiveVsTextViewAsync() + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var vsTextManager = await GetGlobalServiceAsync(); + + ErrorHandler.ThrowOnFailure(vsTextManager.GetActiveView(fMustHaveFocus: 1, pBuffer: null, ppView: out var vsTextView)); + + return vsTextView; + } + + private async Task GetActiveTextViewHostAsync() + { + // The active text view might not have finished composing yet, waiting for the application to 'idle' + // means that it is done pumping messages (including WM_PAINT) and the window should return the correct text + // view. + await WaitForApplicationIdleAsync(CancellationToken.None); + + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var activeVsTextView = (IVsUserData)await GetActiveVsTextViewAsync(); + + ErrorHandler.ThrowOnFailure(activeVsTextView.GetData(IWpfTextViewId, out var wpfTextViewHost)); + + return (IWpfTextViewHost)wpfTextViewHost; + } + + public async Task SetTextAsync(string text) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var view = await GetActiveTextViewAsync(); + var textSnapshot = view.TextSnapshot; + var replacementSpan = new SnapshotSpan(textSnapshot, 0, textSnapshot.Length); + view.TextBuffer.Replace(replacementSpan, text); + } + } +} diff --git a/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/ErrorListExtensions.cs b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/ErrorListExtensions.cs new file mode 100644 index 0000000000..6477d9d2ec --- /dev/null +++ b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/ErrorListExtensions.cs @@ -0,0 +1,73 @@ +// 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.Interop; +using Microsoft.VisualStudio.Shell.TableManager; + +namespace Microsoft.CodeAnalysis.Testing.InProcess +{ + internal static class ErrorListExtensions + { + public static __VSERRORCATEGORY GetCategory(this ITableEntry tableEntry) + { + return tableEntry.GetValueOrDefault(StandardTableKeyNames.ErrorSeverity, (__VSERRORCATEGORY)(-1)); + } + + public static string GetBuildTool(this ITableEntry tableEntry) + { + return tableEntry.GetValueOrDefault(StandardTableKeyNames.BuildTool, ""); + } + + public static string? GetPath(this ITableEntry tableEntry) + { + return tableEntry.GetValueOrDefault(StandardTableKeyNames.Path, null); + } + + public static string? GetDocumentName(this ITableEntry tableEntry) + { + return tableEntry.GetValueOrDefault(StandardTableKeyNames.DocumentName, null); + } + + public static int? GetLine(this ITableEntry tableEntry) + { + return tableEntry.GetValueOrNull(StandardTableKeyNames.Line); + } + + public static int? GetColumn(this ITableEntry tableEntry) + { + return tableEntry.GetValueOrNull(StandardTableKeyNames.Column); + } + + public static string? GetErrorCode(this ITableEntry tableEntry) + { + return tableEntry.GetValueOrDefault(StandardTableKeyNames.ErrorCode, null); + } + + public static string? GetText(this ITableEntry tableEntry) + { + return tableEntry.GetValueOrDefault(StandardTableKeyNames.Text, null); + } + + private static T GetValueOrDefault(this ITableEntry tableEntry, string keyName, T defaultValue) + { + if (!tableEntry.TryGetValue(keyName, out T value)) + { + value = defaultValue; + } + + return value; + } + + private static T? GetValueOrNull(this ITableEntry tableEntry, string keyName) + where T : struct + { + if (!tableEntry.TryGetValue(keyName, out T value)) + { + return null; + } + + return value; + } + } +} diff --git a/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/ErrorListInProcess.cs b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/ErrorListInProcess.cs new file mode 100644 index 0000000000..21fea7306b --- /dev/null +++ b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/ErrorListInProcess.cs @@ -0,0 +1,99 @@ +// 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.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Shell.TableControl; +using Microsoft.VisualStudio.Shell.TableManager; +using Task = System.Threading.Tasks.Task; + +namespace Microsoft.CodeAnalysis.Testing.InProcess +{ + public class ErrorListInProcess : InProcComponent + { + public ErrorListInProcess(TestServices testServices) + : base(testServices) + { + } + + public async Task ShowBuildErrorsAsync() + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var errorList = await GetGlobalServiceAsync(); + errorList.AreBuildErrorSourceEntriesShown = true; + errorList.AreOtherErrorSourceEntriesShown = false; + errorList.AreErrorsShown = true; + errorList.AreMessagesShown = true; + errorList.AreWarningsShown = false; + } + + public async Task> GetBuildErrorsAsync(__VSERRORCATEGORY minimumSeverity = __VSERRORCATEGORY.EC_WARNING) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var errorItems = await GetErrorItemsAsync(); + var list = new List(); + + foreach (var item in errorItems) + { + if (item.GetCategory() > minimumSeverity) + { + continue; + } + + if (!item.TryGetValue(StandardTableKeyNames.ErrorSource, out var errorSource) + || (ErrorSource)errorSource != ErrorSource.Build) + { + continue; + } + + var source = item.GetBuildTool(); + var document = Path.GetFileName(item.GetPath() ?? item.GetDocumentName()) ?? ""; + var line = item.GetLine() ?? -1; + var column = item.GetColumn() ?? -1; + var errorCode = item.GetErrorCode() ?? ""; + var text = item.GetText() ?? ""; + var severity = item.GetCategory() switch + { + __VSERRORCATEGORY.EC_ERROR => "error", + __VSERRORCATEGORY.EC_WARNING => "warning", + __VSERRORCATEGORY.EC_MESSAGE => "info", + var unknown => unknown.ToString(), + }; + + var message = $"({source}) {document}({line + 1}, {column + 1}): {severity} {errorCode}: {text}"; + list.Add(message); + } + + return list + .OrderBy(x => x, StringComparer.OrdinalIgnoreCase) + .ThenBy(x => x, StringComparer.Ordinal) + .ToImmutableArray(); + } + + public async Task GetErrorCountAsync(__VSERRORCATEGORY minimumSeverity = __VSERRORCATEGORY.EC_WARNING) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var errorItems = await GetErrorItemsAsync(); + return errorItems.Count(e => e.GetCategory() <= minimumSeverity); + } + + private async Task> GetErrorItemsAsync() + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var errorList = await GetGlobalServiceAsync(); + var args = await errorList.TableControl.ForceUpdateAsync(); + return args.AllEntries.ToImmutableArray(); + } + } +} diff --git a/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/InProcComponent.cs b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/InProcComponent.cs new file mode 100644 index 0000000000..f489684626 --- /dev/null +++ b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/InProcComponent.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; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Threading; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Threading; +using Task = System.Threading.Tasks.Task; + +namespace Microsoft.CodeAnalysis.Testing.InProcess +{ + public abstract class InProcComponent + { + protected InProcComponent(TestServices testServices) + { + TestServices = testServices ?? throw new ArgumentNullException(nameof(testServices)); + } + + public TestServices TestServices { get; } + + protected JoinableTaskFactory JoinableTaskFactory => TestServices.JoinableTaskFactory; + + protected async Task ExecuteCommandAsync(string commandName, string args = "") + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var dte = await GetGlobalServiceAsync(); + dte.ExecuteCommand(commandName, args); + } + + protected async Task GetGlobalServiceAsync() + where TService : class + where TInterface : class + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var serviceProvider = (IAsyncServiceProvider2)await AsyncServiceProvider.GlobalProvider.GetServiceAsync(typeof(SAsyncServiceProvider)); + Assumes.Present(serviceProvider); + return (TInterface)await serviceProvider.GetServiceAsync(typeof(TService)); + } + + protected async Task GetComponentModelServiceAsync() + where TService : class + { + var componentModel = await GetGlobalServiceAsync(); + return componentModel.GetService(); + } + + /// + /// Waiting for the application to 'idle' means that it is done pumping messages (including WM_PAINT). + /// + /// The cancellation token that the operation will observe. + /// A representing the asynchronous operation. + protected static async Task WaitForApplicationIdleAsync(CancellationToken cancellationToken) + { + var synchronizationContext = new DispatcherSynchronizationContext(Application.Current.Dispatcher, DispatcherPriority.ApplicationIdle); + var taskScheduler = new SynchronizationContextTaskScheduler(synchronizationContext); + await Task.Factory.StartNew( + () => { }, + cancellationToken, + TaskCreationOptions.None, + taskScheduler); + } + } +} diff --git a/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/SolutionExplorerInProcess.cs b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/SolutionExplorerInProcess.cs new file mode 100644 index 0000000000..4bbcf6c0ab --- /dev/null +++ b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/SolutionExplorerInProcess.cs @@ -0,0 +1,578 @@ +// 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; +using System.Threading.Tasks; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.OperationProgress; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudio.Threading; +using NuGet.SolutionRestoreManager; +using Task = System.Threading.Tasks.Task; + +namespace Microsoft.CodeAnalysis.Testing.InProcess +{ + public class SolutionExplorerInProcess : InProcComponent + { + public SolutionExplorerInProcess(TestServices testServices) + : base(testServices) + { + } + + private async Task IsSolutionOpenAsync() + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var solution = await GetGlobalServiceAsync(); + ErrorHandler.ThrowOnFailure(solution.GetProperty((int)__VSPROPID.VSPROPID_IsSolutionOpen, out var isOpen)); + return (bool)isOpen; + } + + public async Task CloseSolutionAsync(bool saveFirst = false) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + if (!await IsSolutionOpenAsync()) + { + return; + } + + if (saveFirst) + { + await SaveSolutionAsync(); + } + + await CloseSolutionAsync(); + } + + private async Task CloseSolutionAsync() + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var solution = await GetGlobalServiceAsync(); + if (!await IsSolutionOpenAsync()) + { + return; + } + + using var semaphore = new SemaphoreSlim(1); + using var solutionEvents = new SolutionEvents(JoinableTaskFactory, solution); + + await semaphore.WaitAsync(); + + void HandleAfterCloseSolution(object sender, EventArgs e) => semaphore.Release(); + + solutionEvents.AfterCloseSolution += HandleAfterCloseSolution; + try + { + ErrorHandler.ThrowOnFailure(solution.CloseSolutionElement((uint)__VSSLNCLOSEOPTIONS.SLNCLOSEOPT_DeleteProject | (uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_NoSave, null, 0)); + await semaphore.WaitAsync(); + } + finally + { + solutionEvents.AfterCloseSolution -= HandleAfterCloseSolution; + } + } + + public async Task CreateSolutionAsync(string solutionName) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var solutionPath = CreateTemporaryPath(); + await CreateSolutionAsync(solutionPath, solutionName); + } + + private async Task CreateSolutionAsync(string solutionPath, string solutionName) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + await CloseSolutionAsync(); + + var solutionFileName = Path.ChangeExtension(solutionName, ".sln"); + Directory.CreateDirectory(solutionPath); + + var solution = await GetGlobalServiceAsync(); + ErrorHandler.ThrowOnFailure(solution.CreateSolution(solutionPath, solutionFileName, (uint)__VSCREATESOLUTIONFLAGS.CSF_SILENT)); + ErrorHandler.ThrowOnFailure(solution.SaveSolutionElement((uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_ForceSave, null, 0)); + } + + public async Task SaveSolutionAsync() + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + if (!await IsSolutionOpenAsync()) + { + throw new InvalidOperationException("Cannot save solution when no solution is open."); + } + + var solution = await GetGlobalServiceAsync(); + + // Make sure the directory exists so the Save dialog doesn't appear + ErrorHandler.ThrowOnFailure(solution.GetSolutionInfo(out var solutionDirectory, out _, out _)); + Directory.CreateDirectory(solutionDirectory); + + ErrorHandler.ThrowOnFailure(solution.SaveSolutionElement((uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_ForceSave, null, 0)); + } + + private static string ConvertLanguageName(string languageName) + { + return languageName switch + { + LanguageNames.CSharp => "CSharp", + LanguageNames.VisualBasic => "VisualBasic", + _ => throw new ArgumentException($"'{languageName}' is not supported.", nameof(languageName)), + }; + } + + private async Task GetDirectoryNameAsync() + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var dte = await GetGlobalServiceAsync(); + var solution = (EnvDTE80.Solution2)dte.Solution; + var solutionFullName = solution.FullName; + var solutionFileFullPath = string.IsNullOrEmpty(solutionFullName) + ? throw new InvalidOperationException() + : solutionFullName; + + return Path.GetDirectoryName(solutionFileFullPath); + } + + private async Task> GetCSharpProjectTemplatesAsync() + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var dte = await GetGlobalServiceAsync(); + var localeID = dte.LocaleID; + + var builder = ImmutableDictionary.CreateBuilder(); + builder[WellKnownProjectTemplates.ClassLibrary] = $@"Windows\{localeID}\ClassLibrary.zip"; + builder[WellKnownProjectTemplates.ConsoleApplication] = "Microsoft.CSharp.ConsoleApplication"; + builder[WellKnownProjectTemplates.Website] = "EmptyWeb.zip"; + builder[WellKnownProjectTemplates.WinFormsApplication] = "WindowsApplication.zip"; + builder[WellKnownProjectTemplates.WpfApplication] = "WpfApplication.zip"; + builder[WellKnownProjectTemplates.WebApplication] = "WebApplicationProject40"; + return builder.ToImmutable(); + } + + private async Task> GetVisualBasicProjectTemplatesAsync() + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var dte = await GetGlobalServiceAsync(); + var localeID = dte.LocaleID; + + var builder = ImmutableDictionary.CreateBuilder(); + builder[WellKnownProjectTemplates.ClassLibrary] = $@"Windows\{localeID}\ClassLibrary.zip"; + builder[WellKnownProjectTemplates.ConsoleApplication] = "Microsoft.VisualBasic.Windows.ConsoleApplication"; + builder[WellKnownProjectTemplates.Website] = "EmptyWeb.zip"; + builder[WellKnownProjectTemplates.WinFormsApplication] = "WindowsApplication.zip"; + builder[WellKnownProjectTemplates.WpfApplication] = "WpfApplication.zip"; + builder[WellKnownProjectTemplates.WebApplication] = "WebApplicationProject40"; + return builder.ToImmutable(); + } + + public async Task AddProjectAsync(string projectName, string projectTemplate, string languageName) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var projectPath = Path.Combine(await GetDirectoryNameAsync(), projectName); + var projectTemplatePath = await GetProjectTemplatePathAsync(projectTemplate, ConvertLanguageName(languageName)); + var solution = await GetGlobalServiceAsync(); + ErrorHandler.ThrowOnFailure(solution.AddNewProjectFromTemplate(projectTemplatePath, null, null, projectPath, projectName, null, out _)); + } + + private async Task GetProjectTemplatePathAsync(string projectTemplate, string languageName) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var dte = await GetGlobalServiceAsync(); + var solution = (EnvDTE80.Solution2)dte.Solution; + + if (string.Equals(languageName, "csharp", StringComparison.OrdinalIgnoreCase) + && (await GetCSharpProjectTemplatesAsync()).TryGetValue(projectTemplate, out var csharpProjectTemplate)) + { + return solution.GetProjectTemplate(csharpProjectTemplate, languageName); + } + + if (string.Equals(languageName, "visualbasic", StringComparison.OrdinalIgnoreCase) + && (await GetVisualBasicProjectTemplatesAsync()).TryGetValue(projectTemplate, out var visualBasicProjectTemplate)) + { + return solution.GetProjectTemplate(visualBasicProjectTemplate, languageName); + } + + return solution.GetProjectTemplate(projectTemplate, languageName); + } + + public async Task RestoreNuGetPackagesAsync(CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var dte = await GetGlobalServiceAsync(); + var solution = (EnvDTE80.Solution2)dte.Solution; + foreach (var project in solution.Projects.OfType()) + { + await RestoreNuGetPackagesAsync(project.FullName, cancellationToken); + } + } + + public async Task RestoreNuGetPackagesAsync(string projectName, CancellationToken cancellationToken) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var operationProgressStatus = await GetGlobalServiceAsync(); + var stageStatus = operationProgressStatus.GetStageStatus(CommonOperationProgressStageIds.Intellisense); + await stageStatus.WaitForCompletionAsync(); + + var solutionRestoreService = await GetComponentModelServiceAsync(); + await solutionRestoreService.CurrentRestoreOperation; + + var projectFullPath = (await GetProjectAsync(projectName)).FullName; + var solutionRestoreStatusProvider = await GetComponentModelServiceAsync(); + if (await solutionRestoreStatusProvider.IsRestoreCompleteAsync(cancellationToken)) + { + return; + } + + var solutionRestoreService2 = (IVsSolutionRestoreService2)solutionRestoreService; + await solutionRestoreService2.NominateProjectAsync(projectFullPath, cancellationToken); + + while (true) + { + if (await solutionRestoreStatusProvider.IsRestoreCompleteAsync(cancellationToken)) + { + return; + } + + await Task.Delay(TimeSpan.FromMilliseconds(50), cancellationToken); + } + } + + public async Task BuildSolutionAsync(bool waitForBuildToFinish) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var buildOutputWindowPane = await GetBuildOutputWindowPaneAsync(); + buildOutputWindowPane.Clear(); + + await ExecuteCommandAsync(WellKnownCommandNames.Build.BuildSolution); + if (waitForBuildToFinish) + { + return await WaitForBuildToFinishAsync(buildOutputWindowPane); + } + + return null; + } + + public async Task WaitForBuildToFinishAsync() + { + var buildOutputWindowPane = await GetBuildOutputWindowPaneAsync(); + return await WaitForBuildToFinishAsync(buildOutputWindowPane); + } + + public async Task GetBuildOutputWindowPaneAsync() + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var outputWindow = await GetGlobalServiceAsync(); + ErrorHandler.ThrowOnFailure(outputWindow.GetPane(VSConstants.OutputWindowPaneGuid.BuildOutputPane_guid, out var pane)); + return pane; + } + + private async Task WaitForBuildToFinishAsync(IVsOutputWindowPane buildOutputWindowPane) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var buildManager = await GetGlobalServiceAsync(); + using var semaphore = new SemaphoreSlim(1); + using var solutionEvents = new UpdateSolutionEvents(buildManager); + + await semaphore.WaitAsync(); + + void HandleUpdateSolutionDone(bool succeeded, bool modified, bool canceled) => semaphore.Release(); + solutionEvents.OnUpdateSolutionDone += HandleUpdateSolutionDone; + try + { + await semaphore.WaitAsync(); + } + finally + { + solutionEvents.OnUpdateSolutionDone -= HandleUpdateSolutionDone; + } + + // Force the error list to update + ErrorHandler.ThrowOnFailure(buildOutputWindowPane.FlushToTaskList()); + + var textView = (IVsTextView)buildOutputWindowPane; + ErrorHandler.ThrowOnFailure(((IVsUserData)textView).GetData(EditorInProcess.IWpfTextViewId, out var wpfTextViewHost)); + var lines = ((IWpfTextViewHost)wpfTextViewHost).TextView.TextViewLines; + if (lines.Count < 1) + { + return string.Empty; + } + + return lines[lines.Count - 2].Extent.GetText(); + } + + private string CreateTemporaryPath() + { + return Path.Combine(Path.GetTempPath(), "roslyn-sdk-test", Path.GetRandomFileName()); + } + + private async Task GetProjectAsync(string nameOrFileName) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + + var dte = await GetGlobalServiceAsync(); + var solution = (EnvDTE80.Solution2)dte.Solution; + return solution.Projects.OfType().First( + project => + { + ThreadHelper.ThrowIfNotOnUIThread(); + return string.Equals(project.FileName, nameOrFileName, StringComparison.OrdinalIgnoreCase) + || string.Equals(project.Name, nameOrFileName, StringComparison.OrdinalIgnoreCase); + }); + } + + private sealed class SolutionEvents : IVsSolutionEvents, IDisposable + { + private readonly JoinableTaskFactory _joinableTaskFactory; + private readonly IVsSolution _solution; + private readonly uint _cookie; + + public SolutionEvents(JoinableTaskFactory joinableTaskFactory, IVsSolution solution) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + _joinableTaskFactory = joinableTaskFactory; + _solution = solution; + ErrorHandler.ThrowOnFailure(solution.AdviseSolutionEvents(this, out _cookie)); + } + + public event EventHandler? AfterCloseSolution; + + public void Dispose() + { + _joinableTaskFactory.Run(async () => + { + await _joinableTaskFactory.SwitchToMainThreadAsync(); + ErrorHandler.ThrowOnFailure(_solution.UnadviseSolutionEvents(_cookie)); + }); + } + + public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) + { + return VSConstants.S_OK; + } + + public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel) + { + return VSConstants.S_OK; + } + + public int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved) + { + return VSConstants.S_OK; + } + + public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy) + { + return VSConstants.S_OK; + } + + public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel) + { + return VSConstants.S_OK; + } + + public int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy) + { + return VSConstants.S_OK; + } + + public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution) + { + return VSConstants.S_OK; + } + + public int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel) + { + return VSConstants.S_OK; + } + + public int OnBeforeCloseSolution(object pUnkReserved) + { + return VSConstants.S_OK; + } + + public int OnAfterCloseSolution(object pUnkReserved) + { + AfterCloseSolution?.Invoke(this, EventArgs.Empty); + return VSConstants.S_OK; + } + } + + internal sealed class UpdateSolutionEvents : IVsUpdateSolutionEvents, IVsUpdateSolutionEvents2, IDisposable + { + private uint _cookie; + private IVsSolutionBuildManager2 _solutionBuildManager; + + internal delegate void UpdateSolutionDoneEvent(bool succeeded, bool modified, bool canceled); + + internal delegate void UpdateSolutionBeginEvent(ref bool cancel); + + internal delegate void UpdateSolutionStartUpdateEvent(ref bool cancel); + + internal delegate void UpdateProjectConfigDoneEvent(IVsHierarchy projectHierarchy, IVsCfg projectConfig, int success); + + internal delegate void UpdateProjectConfigBeginEvent(IVsHierarchy projectHierarchy, IVsCfg projectConfig); + + public event UpdateSolutionDoneEvent? OnUpdateSolutionDone; + + public event UpdateSolutionBeginEvent? OnUpdateSolutionBegin; + + public event UpdateSolutionStartUpdateEvent? OnUpdateSolutionStartUpdate; + + public event Action? OnActiveProjectConfigurationChange; + + public event Action? OnUpdateSolutionCancel; + + public event UpdateProjectConfigDoneEvent? OnUpdateProjectConfigDone; + + public event UpdateProjectConfigBeginEvent? OnUpdateProjectConfigBegin; + + internal UpdateSolutionEvents(IVsSolutionBuildManager2 solutionBuildManager) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + _solutionBuildManager = solutionBuildManager; + ErrorHandler.ThrowOnFailure(solutionBuildManager.AdviseUpdateSolutionEvents(this, out _cookie)); + } + + int IVsUpdateSolutionEvents.UpdateSolution_Begin(ref int pfCancelUpdate) + { + var cancel = false; + OnUpdateSolutionBegin?.Invoke(ref cancel); + if (cancel) + { + pfCancelUpdate = 1; + } + + return 0; + } + + int IVsUpdateSolutionEvents.UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand) + { + OnUpdateSolutionDone?.Invoke(fSucceeded != 0, fModified != 0, fCancelCommand != 0); + return 0; + } + + int IVsUpdateSolutionEvents.UpdateSolution_StartUpdate(ref int pfCancelUpdate) + { + return UpdateSolution_StartUpdate(ref pfCancelUpdate); + } + + int IVsUpdateSolutionEvents.UpdateSolution_Cancel() + { + OnUpdateSolutionCancel?.Invoke(); + return 0; + } + + int IVsUpdateSolutionEvents.OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) + { + return OnActiveProjectCfgChange(pIVsHierarchy); + } + + int IVsUpdateSolutionEvents2.UpdateSolution_Begin(ref int pfCancelUpdate) + { + var cancel = false; + OnUpdateSolutionBegin?.Invoke(ref cancel); + if (cancel) + { + pfCancelUpdate = 1; + } + + return 0; + } + + int IVsUpdateSolutionEvents2.UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand) + { + OnUpdateSolutionDone?.Invoke(fSucceeded != 0, fModified != 0, fCancelCommand != 0); + return 0; + } + + int IVsUpdateSolutionEvents2.UpdateSolution_StartUpdate(ref int pfCancelUpdate) + { + return UpdateSolution_StartUpdate(ref pfCancelUpdate); + } + + int IVsUpdateSolutionEvents2.UpdateSolution_Cancel() + { + OnUpdateSolutionCancel?.Invoke(); + return 0; + } + + int IVsUpdateSolutionEvents2.OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) + { + return OnActiveProjectCfgChange(pIVsHierarchy); + } + + int IVsUpdateSolutionEvents2.UpdateProjectCfg_Begin(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, ref int pfCancel) + { + OnUpdateProjectConfigBegin?.Invoke(pHierProj, pCfgProj); + return 0; + } + + int IVsUpdateSolutionEvents2.UpdateProjectCfg_Done(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, int fSuccess, int fCancel) + { + OnUpdateProjectConfigDone?.Invoke(pHierProj, pCfgProj, fSuccess); + return 0; + } + + private int UpdateSolution_StartUpdate(ref int pfCancelUpdate) + { + var cancel = false; + OnUpdateSolutionStartUpdate?.Invoke(ref cancel); + if (cancel) + { + pfCancelUpdate = 1; + } + + return 0; + } + + private int OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) + { + OnActiveProjectConfigurationChange?.Invoke(); + return 0; + } + + void IDisposable.Dispose() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + OnUpdateSolutionDone = null; + OnUpdateSolutionBegin = null; + OnUpdateSolutionStartUpdate = null; + OnActiveProjectConfigurationChange = null; + OnUpdateSolutionCancel = null; + OnUpdateProjectConfigDone = null; + OnUpdateProjectConfigBegin = null; + + if (_cookie != 0) + { + var tempCookie = _cookie; + _cookie = 0; + ErrorHandler.ThrowOnFailure(_solutionBuildManager.UnadviseUpdateSolutionEvents(tempCookie)); + } + } + } + } +} diff --git a/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/SynchronizationContextTaskScheduler.cs b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/SynchronizationContextTaskScheduler.cs new file mode 100644 index 0000000000..95f99b12a0 --- /dev/null +++ b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/SynchronizationContextTaskScheduler.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 System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.Testing.InProcess +{ + // Based on CoreCLR's implementation of the TaskScheduler they return from TaskScheduler.FromCurrentSynchronizationContext + internal class SynchronizationContextTaskScheduler : TaskScheduler + { + private readonly SendOrPostCallback _postCallback; + private readonly SynchronizationContext _synchronizationContext; + + internal SynchronizationContextTaskScheduler(SynchronizationContext synchronizationContext) + { + _postCallback = new SendOrPostCallback(PostCallback); + _synchronizationContext = synchronizationContext ?? throw new ArgumentNullException(nameof(synchronizationContext)); + } + + public override int MaximumConcurrencyLevel => 1; + + protected override void QueueTask(Task task) + { +#pragma warning disable VSTHRD001 // Avoid legacy thread switching APIs + _synchronizationContext.Post(_postCallback, task); +#pragma warning restore VSTHRD001 // Avoid legacy thread switching APIs + } + + protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) + { + if (SynchronizationContext.Current == _synchronizationContext) + { + return TryExecuteTask(task); + } + + return false; + } + + protected override IEnumerable? GetScheduledTasks() + => null; + + private void PostCallback(object obj) + => TryExecuteTask((Task)obj); + } +} diff --git a/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/TestServices.cs b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/TestServices.cs new file mode 100644 index 0000000000..160a3e779e --- /dev/null +++ b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/InProcess/TestServices.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.Threading.Tasks; +using Microsoft.VisualStudio.Threading; + +namespace Microsoft.CodeAnalysis.Testing.InProcess +{ + public class TestServices + { + protected TestServices(JoinableTaskFactory joinableTaskFactory) + { + JoinableTaskFactory = joinableTaskFactory; + + Editor = new EditorInProcess(this); + ErrorList = new ErrorListInProcess(this); + SolutionExplorer = new SolutionExplorerInProcess(this); + } + + public JoinableTaskFactory JoinableTaskFactory { get; } + + public EditorInProcess Editor { get; } + + public ErrorListInProcess ErrorList { get; } + + public SolutionExplorerInProcess SolutionExplorer { get; } + + internal static async Task CreateAsync(JoinableTaskFactory joinableTaskFactory) + { + var services = new TestServices(joinableTaskFactory); + await services.InitializeAsync(); + return services; + } + + protected virtual Task InitializeAsync() + { + return Task.CompletedTask; + } + } +} diff --git a/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/Roslyn.SDK.IntegrationTests.csproj b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/Roslyn.SDK.IntegrationTests.csproj new file mode 100644 index 0000000000..d8d0265eb4 --- /dev/null +++ b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/Roslyn.SDK.IntegrationTests.csproj @@ -0,0 +1,45 @@ + + + + net472 + true + + Microsoft.CodeAnalysis.Testing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/WellKnownCommandNames.cs b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/WellKnownCommandNames.cs new file mode 100644 index 0000000000..3c162a8d4a --- /dev/null +++ b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/WellKnownCommandNames.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. + +namespace Microsoft.CodeAnalysis.Testing +{ + internal static class WellKnownCommandNames + { + public static class Build + { + public const string BuildSolution = "Build.BuildSolution"; + } + } +} diff --git a/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/WellKnownProjectTemplates.cs b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/WellKnownProjectTemplates.cs new file mode 100644 index 0000000000..504eb8d6e5 --- /dev/null +++ b/tests/VisualStudio.Roslyn.SDK/Roslyn.SDK.IntegrationTests/WellKnownProjectTemplates.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. + +namespace Microsoft.CodeAnalysis.Testing +{ + internal static class WellKnownProjectTemplates + { + public const string ClassLibrary = nameof(ClassLibrary); + public const string ConsoleApplication = nameof(ConsoleApplication); + public const string Website = nameof(Website); + public const string WinFormsApplication = nameof(WinFormsApplication); + public const string WpfApplication = nameof(WpfApplication); + public const string WebApplication = nameof(WebApplication); + public const string CSharpNetCoreClassLibrary = "Microsoft.CSharp.NETCore.ClassLibrary"; + public const string VisualBasicNetCoreClassLibrary = "Microsoft.VisualBasic.NETCore.ClassLibrary"; + public const string CSharpNetCoreConsoleApplication = "Microsoft.CSharp.NETCore.ConsoleApplication"; + public const string VisualBasicNetCoreConsoleApplication = "Microsoft.VisualBasic.NETCore.ConsoleApplication"; + public const string CSharpNetCoreUnitTest = "Microsoft.CSharp.NETCore.UnitTest"; + public const string CSharpNetCoreXUnitTest = "Microsoft.CSharp.NETCore.XUnitTest"; + } +}