diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a1555a6e7..c7f035d54f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,31 @@ # Changelog All changes to the project will be documented in this file. +## [1.37.2] - Not Yet Released +* Add support for new quick info endpoint when working with Cake (PR: [#1945](https://github.com/OmniSharp/omnisharp-roslyn/pull/1945)) +* Add support for new completion endpoints when working with Cake ([#1939](https://github.com/OmniSharp/omnisharp-roslyn/issues/1939), PR: [#1944](https://github.com/OmniSharp/omnisharp-roslyn/pull/1944)) + +## [1.37.1] - 2020-09-01 +* Ensure that all quickinfo sections have linebreaks between them, and don't add unecessary duplicate linebreaks (PR: [#1900](https://github.com/OmniSharp/omnisharp-roslyn/pull/1900)) +* Support completion of unimported types (PR: [#1896](https://github.com/OmniSharp/omnisharp-roslyn/pull/1896)) +* Exclude Misc project from InternalsVisibleTo completion (PR: [#1902](https://github.com/OmniSharp/omnisharp-roslyn/pull/1902)) +* Ensure unimported things are sorted after imported things (PR: [#1903](https://github.com/OmniSharp/omnisharp-roslyn/pull/1903)) +* Updated lsp library to fix issue with signature help, etc ([#1887](https://github.com/OmniSharp/omnisharp-roslyn/issues/1887), PR: [#1890](https://github.com/OmniSharp/omnisharp-roslyn/pull/1890)) +* Correctly handle multiple reference aliases (PR: [#1905](https://github.com/OmniSharp/omnisharp-roslyn/pull/1905)) +* Better handle completion when the display text is not in the final result (PR: [#1908](https://github.com/OmniSharp/omnisharp-roslyn/pull/1908)) +* Correctly mark hover markup content as markdown ([#1906](https://github.com/OmniSharp/omnisharp-roslyn/issues/1906), PR: [#1909](https://github.com/OmniSharp/omnisharp-roslyn/pull/1909)) +* Upgrade lsp ([#1898](https://github.com/OmniSharp/omnisharp-roslyn/issues/1898), PR: [#1911](https://github.com/OmniSharp/omnisharp-roslyn/pull/1911)) +* Updated to ILSpy 6.1.0.5902 (PR: [#1913](https://github.com/OmniSharp/omnisharp-roslyn/pull/1913)) +* Updated to NET 5.0 preview8 (PR: [#1916](https://github.com/OmniSharp/omnisharp-roslyn/pull/1916)) +* Add HTTP Driver back to build.json (PR: [#1918](https://github.com/OmniSharp/omnisharp-roslyn/pull/1918)) +* Use ExecutionPolicy Bypass when running powershell.exe (PR: [#1917](https://github.com/OmniSharp/omnisharp-roslyn/pull/1917)) +* Update the package that Arch Linux users need to install (PR: [#1921](https://github.com/OmniSharp/omnisharp-roslyn/pull/1921)) +* Updated the docs to mention .NET 4.7.2 targeting pack (PR: [#1922](https://github.com/OmniSharp/omnisharp-roslyn/pull/1922)) +* Support for configurations remapping in solution files ([#1828](https://github.com/OmniSharp/omnisharp-roslyn/issues/1828), PR: [#1835](https://github.com/OmniSharp/omnisharp-roslyn/pull/1835)) +* Only run dotnet --info once for the working directory (PR: [#1925](https://github.com/OmniSharp/omnisharp-roslyn/pull/1925)) +* Update build tool versions for NET 5 RC1 (PR: [#1926](https://github.com/OmniSharp/omnisharp-roslyn/pull/1926)) +* Update Roslyn to 3.8.0-3.20451.2 (PR: [#1927](https://github.com/OmniSharp/omnisharp-roslyn/pull/1927)) + ## [1.37.0] - 2020-08-18 * Update Roslyn version and tooling to match .NET 5 Preview8 (PR: [#1897](https://github.com/OmniSharp/omnisharp-roslyn/pull/1897)) * Updated lsp library to fix issue with signature help, etc (PR: [#1890](https://github.com/OmniSharp/omnisharp-roslyn/pull/1890)) diff --git a/README.md b/README.md index 6b5c9253c4..f38be878d0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ OmniSharp is a .NET development platform based on [Roslyn](https://github.com/dotnet/roslyn) workspaces. It provides project dependencies and C# language services to various IDEs and plugins. -OmniSharp is built with the [.NET Core SDK](https://dot.net/) on Windows and [Mono](http://www.mono-project.com/) on OSX/Linux. It targets the _net472_ target framework. For platforms other than Windows, OmniSharp ships with an _embedded Mono_ which is based on version _5.18.0_, includes MSBuild _16.3.0_ and is provisioned during the build script. If _Mono_ is globally installed on the system, OmniSharp will prefer it over the embedded version, however version _>=6.4.0_ is required (the lowest version with at least MSBuild _16.3.0_). +OmniSharp is built with the [.NET Core SDK](https://dot.net/) on Windows and [Mono](http://www.mono-project.com/) on OSX/Linux. It targets the _net472_ target framework. For platforms other than Windows, OmniSharp ships with an _embedded Mono_ which is based on version _6.10.0_, includes MSBuild _16.8.0_ and is provisioned during the build script. If _Mono_ is globally installed on the system, OmniSharp will prefer it over the embedded version, however version _>=6.4.0_ is required (the lowest version with at least MSBuild _16.3.0_). For Arch Linux users, you need package [mono-msbuild](https://www.archlinux.org/packages/community/x86_64/mono-msbuild/) (>= 16.3). @@ -64,7 +64,7 @@ For more details, see [Build](https://github.com/OmniSharp/omnisharp-roslyn/blob ### VS Code -Add the following setting to your [User Settings or Workspace Settings](https://code.visualstudio.com/Docs/customization/userandworkspace). +Add the following setting to your [User Settings](https://code.visualstudio.com/Docs/customization/userandworkspace). ```JSON { @@ -77,7 +77,7 @@ The above option can also be set to: - "latest" - To consume the latest build from the master branch - A specific version number like `1.29.2-beta.60` -In order to be able to attach a debugger, add the following setting: +In order to be able to attach a debugger, add the following setting to your [User or Workspace settings](https://code.visualstudio.com/Docs/customization/userandworkspace): ```JSON { diff --git a/build.cake b/build.cake index 4e71c97eef..0d32185a5e 100644 --- a/build.cake +++ b/build.cake @@ -392,6 +392,10 @@ Task("CreateMSBuildFolder") source: CombinePaths(msbuildSdkResolverSourceFolder, "Microsoft.DotNet.MSBuildSdkResolver.dll"), destination: CombinePaths(msbuildSdkResolverTargetFolder, "Microsoft.DotNet.MSBuildSdkResolver.dll")); + FileHelper.Copy( + source: CombinePaths(msbuildSdkResolverSourceFolder, "Microsoft.DotNet.SdkResolver.dll"), + destination: CombinePaths(msbuildSdkResolverTargetFolder, "Microsoft.DotNet.SdkResolver.dll")); + if (Platform.Current.IsWindows) { CopyDotNetHostResolver(env, "win", "x86", "hostfxr.dll", msbuildSdkResolverTargetFolder, copyToArchSpecificFolder: true); @@ -906,7 +910,6 @@ Task("Publish") /// Execute the run script. /// Task("ExecuteRunScript") - .WithCriteria(() => !(Platform.Current.IsMacOS && TravisCI.IsRunningOnTravisCI)) .Does(() => { // TODO: Pass configuration into run script to ensure that MSBuild output paths are handled correctly. diff --git a/build.cmd b/build.cmd index 47ddeee53f..20899d195e 100644 --- a/build.cmd +++ b/build.cmd @@ -1,2 +1,2 @@ @echo off -powershell.exe -NoProfile -ExecutionPolicy Bypass .\build.ps1 "%*" +powershell.exe -NoProfile -ExecutionPolicy Bypass %~dp0build.ps1 "%*" diff --git a/build.json b/build.json index 094b1f2082..3fe86bbf43 100644 --- a/build.json +++ b/build.json @@ -3,7 +3,7 @@ "DotNetChannel": "Preview", "DotNetVersions": [ "3.1.401", - "5.0.100-preview.8.20417.9" + "5.0.100-rc.1.20452.10" ], "RequiredMonoVersion": "6.6.0", "DownloadURL": "https://roslynomnisharp.blob.core.windows.net/ext", diff --git a/build.ps1 b/build.ps1 index fcd414b119..737c071f71 100644 --- a/build.ps1 +++ b/build.ps1 @@ -42,7 +42,7 @@ http://cakebuild.net Param( [parameter(position=0)] [string]$Target = "Default", - [string]$Script = "build.cake", + [string]$Script = "$PSScriptRoot/build.cake", [ValidateSet("Release", "Debug")] [string]$Configuration = "Debug", [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] @@ -85,10 +85,6 @@ function MD5HashFile([string] $filePath) Write-Host "Preparing to run build script..." -if(!$PSScriptRoot){ - $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent -} - $TOOLS_DIR = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot "tools")) $NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" $CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" diff --git a/build/Packages.props b/build/Packages.props index 122503d415..40989323f3 100644 --- a/build/Packages.props +++ b/build/Packages.props @@ -5,8 +5,8 @@ 16.6.0 5.2.0 - 3.8.0-1.20363.1 - 2.4.0 + 3.8.0-3.20451.2 + 2.4.1 @@ -78,7 +78,7 @@ - + diff --git a/src/OmniSharp.Cake/Extensions/ResponseExtensions.cs b/src/OmniSharp.Cake/Extensions/ResponseExtensions.cs index 2e3bf8772c..ee2fba4ce5 100644 --- a/src/OmniSharp.Cake/Extensions/ResponseExtensions.cs +++ b/src/OmniSharp.Cake/Extensions/ResponseExtensions.cs @@ -8,6 +8,7 @@ using OmniSharp.Models.Navigate; using OmniSharp.Models.MembersTree; using OmniSharp.Models.Rename; +using OmniSharp.Models.v1.Completion; using OmniSharp.Models.V2; using OmniSharp.Models.V2.CodeActions; using OmniSharp.Models.V2.CodeStructure; @@ -27,22 +28,6 @@ public static QuickFixResponse OnlyThisFile(this QuickFixResponse response, stri var quickFixes = response.QuickFixes.Where(x => PathsAreEqual(x.FileName, fileName)); response.QuickFixes = quickFixes; return response; - - bool PathsAreEqual(string x, string y) - { - if (x == null && y == null) - { - return true; - } - if (x == null || y == null) - { - return false; - } - - var comparer = PlatformHelper.IsWindows ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - - return Path.GetFullPath(x).Equals(Path.GetFullPath(y), comparer); - } } public static Task TranslateAsync(this QuickFixResponse response, OmniSharpWorkspace workspace) @@ -211,6 +196,38 @@ public static async Task TranslateAsync(this BlockStruct return response; } + public static async Task TranslateAsync(this CompletionResponse response, OmniSharpWorkspace workspace, CompletionRequest request) + { + foreach (var item in response.Items) + { + if (item.AdditionalTextEdits is null) + { + continue; + } + + List additionalTextEdits = null; + + foreach (var additionalTextEdit in item.AdditionalTextEdits) + { + var (_, change) = await additionalTextEdit.TranslateAsync(workspace, request.FileName); + + // Due to the fact that AdditionalTextEdits return the complete buffer, we can't currently use that in Cake. + // Revisit when we have a solution. At this point it's probably just best to remove AdditionalTextEdits. + if (change.StartLine < 0) + { + continue; + } + + additionalTextEdits ??= new List(); + additionalTextEdits.Add(change); + } + + item.AdditionalTextEdits = additionalTextEdits; + } + + return response; + } + private static async Task TranslateAsync(this CodeElement element, OmniSharpWorkspace workspace, SimpleFileRequest request) { var builder = new CodeElement.Builder @@ -345,5 +362,21 @@ private static async Task PopulateModificationsAsync( return (newFileName, change); } + + private static bool PathsAreEqual(string x, string y) + { + if (x == null && y == null) + { + return true; + } + if (x == null || y == null) + { + return false; + } + + var comparer = PlatformHelper.IsWindows ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + + return Path.GetFullPath(x).Equals(Path.GetFullPath(y), comparer); + } } } diff --git a/src/OmniSharp.Cake/Services/RequestHandlers/Completion/CompletionHandler.cs b/src/OmniSharp.Cake/Services/RequestHandlers/Completion/CompletionHandler.cs new file mode 100644 index 0000000000..5de5fe1878 --- /dev/null +++ b/src/OmniSharp.Cake/Services/RequestHandlers/Completion/CompletionHandler.cs @@ -0,0 +1,46 @@ +using System.Composition; +using System.Linq; +using System.Threading.Tasks; +using OmniSharp.Cake.Extensions; +using OmniSharp.Mef; +using OmniSharp.Models.v1.Completion; + +namespace OmniSharp.Cake.Services.RequestHandlers.Completion +{ + [Shared] + [OmniSharpHandler(OmniSharpEndpoints.Completion, Constants.LanguageNames.Cake)] + public class CompletionHandler : CakeRequestHandler + { + [ImportingConstructor] + public CompletionHandler(OmniSharpWorkspace workspace) : base(workspace) + { + } + + protected override Task TranslateResponse(CompletionResponse response, CompletionRequest request) + { + return response.TranslateAsync(Workspace, request); + } + } + + [Shared] + [OmniSharpHandler(OmniSharpEndpoints.CompletionResolve, Constants.LanguageNames.Cake)] + public class CompletionResolveHandler : CakeRequestHandler + { + [ImportingConstructor] + public CompletionResolveHandler(OmniSharpWorkspace workspace) : base(workspace) + { + } + + protected override Task TranslateResponse(CompletionResolveResponse response, CompletionResolveRequest request) + { + // Due to the fact that AdditionalTextEdits return the complete buffer, we can't currently use that in Cake. + // Revisit when we have a solution. At this point it's probably just best to remove AdditionalTextEdits. + if (response.Item is object) + { + response.Item.AdditionalTextEdits = null; + } + + return Task.FromResult(response); + } + } +} diff --git a/src/OmniSharp.Cake/Services/RequestHandlers/QuickInfoHandler.cs b/src/OmniSharp.Cake/Services/RequestHandlers/QuickInfoHandler.cs new file mode 100644 index 0000000000..b3aa32f193 --- /dev/null +++ b/src/OmniSharp.Cake/Services/RequestHandlers/QuickInfoHandler.cs @@ -0,0 +1,16 @@ +using System.Composition; +using OmniSharp.Mef; +using OmniSharp.Models; + +namespace OmniSharp.Cake.Services.RequestHandlers +{ + [Shared] + [OmniSharpHandler(OmniSharpEndpoints.QuickInfo, Constants.LanguageNames.Cake)] + public class QuickInfoHandler : CakeRequestHandler + { + [ImportingConstructor] + public QuickInfoHandler(OmniSharpWorkspace workspace) : base(workspace) + { + } + } +} diff --git a/src/OmniSharp.LanguageServerProtocol/Handlers/OmniSharpCompletionHandler.cs b/src/OmniSharp.LanguageServerProtocol/Handlers/OmniSharpCompletionHandler.cs index 1759499372..dfa8b1b37e 100644 --- a/src/OmniSharp.LanguageServerProtocol/Handlers/OmniSharpCompletionHandler.cs +++ b/src/OmniSharp.LanguageServerProtocol/Handlers/OmniSharpCompletionHandler.cs @@ -27,10 +27,10 @@ public static IEnumerable Enumerate(RequestHandlers handlers) private static readonly IDictionary _kind = new Dictionary{ // types { "Class", CompletionItemKind.Class }, - { "Delegate", CompletionItemKind.Class }, // need a better option for this. + { "Delegate", CompletionItemKind.Function }, { "Enum", CompletionItemKind.Enum }, { "Interface", CompletionItemKind.Interface }, - { "Struct", CompletionItemKind.Class }, // TODO: Is struct missing from enum? + { "Struct", CompletionItemKind.Struct }, // variables { "Local", CompletionItemKind.Variable }, @@ -38,15 +38,15 @@ public static IEnumerable Enumerate(RequestHandlers handlers) { "RangeVariable", CompletionItemKind.Variable }, // members - { "Const", CompletionItemKind.Value }, // TODO: Is const missing from enum? + { "Const", CompletionItemKind.Constant }, { "EnumMember", CompletionItemKind.Enum }, - { "Event", CompletionItemKind.Function }, // TODO: Is event missing from enum? + { "Event", CompletionItemKind.Event }, { "Field", CompletionItemKind.Field }, { "Method", CompletionItemKind.Method }, { "Property", CompletionItemKind.Property }, // other stuff - { "Label", CompletionItemKind.Unit }, // need a better option for this. + { "Label", CompletionItemKind.Text }, { "Keyword", CompletionItemKind.Keyword }, { "Namespace", CompletionItemKind.Module } }; diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs index e10f871fee..a95e2d6ad6 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionService.cs @@ -59,7 +59,7 @@ public class CompletionService : { WellKnownTags.Keyword, CompletionItemKind.Keyword }, { WellKnownTags.Label, CompletionItemKind.Text }, { WellKnownTags.Local, CompletionItemKind.Variable }, - { WellKnownTags.Namespace, CompletionItemKind.Text }, + { WellKnownTags.Namespace, CompletionItemKind.Module }, { WellKnownTags.Method, CompletionItemKind.Method }, { WellKnownTags.Module, CompletionItemKind.Module }, { WellKnownTags.Operator, CompletionItemKind.Operator }, @@ -174,6 +174,16 @@ public async Task Handle(CompletionRequest request) string providerName = completion.GetProviderName(); switch (providerName) { + case CompletionItemExtensions.EmeddedLanguageCompletionProvider: + // The Regex completion provider can change escapes based on whether + // we're in a verbatim string or not + { + CompletionChange change = await completionService.GetChangeAsync(document, completion); + Debug.Assert(typedSpan == change.TextChange.Span); + insertText = change.TextChange.NewText!; + } + break; + case CompletionItemExtensions.InternalsVisibleToCompletionProvider: // The IVT completer doesn't add extra things before the completion // span, only assembly keys at the end if they exist. diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Intellisense/CompletionItemExtensions.cs b/src/OmniSharp.Roslyn.CSharp/Services/Intellisense/CompletionItemExtensions.cs index 6ece193da3..e8f828aa6a 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Intellisense/CompletionItemExtensions.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Intellisense/CompletionItemExtensions.cs @@ -26,6 +26,7 @@ internal static class CompletionItemExtensions internal const string XmlDocCommentCompletionProvider = "Microsoft.CodeAnalysis.CSharp.Completion.Providers.XmlDocCommentCompletionProvider"; internal const string TypeImportCompletionProvider = "Microsoft.CodeAnalysis.CSharp.Completion.Providers.TypeImportCompletionProvider"; internal const string ExtensionMethodImportCompletionProvider = "Microsoft.CodeAnalysis.CSharp.Completion.Providers.ExtensionMethodImportCompletionProvider"; + internal const string EmeddedLanguageCompletionProvider = "Microsoft.CodeAnalysis.CSharp.Completion.Providers.EmbeddedLanguageCompletionProvider"; private const string ProviderName = nameof(ProviderName); private const string SymbolCompletionItem = "Microsoft.CodeAnalysis.Completion.Providers.SymbolCompletionItem"; private const string SymbolKind = nameof(SymbolKind); @@ -69,8 +70,9 @@ internal static Task GetChangeAsync( CompletionItem item, TextSpan completionListSpan, char? commitCharacter = null, + bool disallowAddingImports = false, CancellationToken cancellationToken = default) - => (Task)_getChangeAsync.Invoke(completionService, new object[] { document, item, completionListSpan, commitCharacter, cancellationToken }); + => (Task)_getChangeAsync.Invoke(completionService, new object[] { document, item, completionListSpan, commitCharacter, disallowAddingImports, cancellationToken }); public static async Task> GetCompletionSymbolsAsync(this CompletionItem completionItem, IEnumerable recommendedSymbols, Document document) { diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Intellisense/IntellisenseService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Intellisense/IntellisenseService.cs index b042f092d8..82199418bd 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Intellisense/IntellisenseService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Intellisense/IntellisenseService.cs @@ -15,6 +15,7 @@ namespace OmniSharp.Roslyn.CSharp.Services.Intellisense { + [Obsolete("Please use CompletionService.")] [OmniSharpHandler(OmniSharpEndpoints.AutoComplete, LanguageNames.CSharp)] public class IntellisenseService : IRequestHandler> { diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Navigation/FindImplementationsService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Navigation/FindImplementationsService.cs index 2d8e198390..d2f40a73cd 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Navigation/FindImplementationsService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Navigation/FindImplementationsService.cs @@ -39,23 +39,25 @@ public async Task Handle(FindImplementationsRequest request) var quickFixes = new List(); var symbol = await SymbolFinder.FindSymbolAtPositionAsync(semanticModel, position, _workspace); - // SymbolFinder.FindImplementationsAsync will not include the method overrides - var implementations = await SymbolFinder.FindImplementationsAsync(symbol, _workspace.CurrentSolution); - foreach (var implementation in implementations) + if (symbol.IsInterfaceType() || symbol.IsImplementableMember()) { - quickFixes.Add(implementation, _workspace); - - if (implementation.IsOverridable()) + // SymbolFinder.FindImplementationsAsync will not include the method overrides + var implementations = await SymbolFinder.FindImplementationsAsync(symbol, _workspace.CurrentSolution); + foreach (var implementation in implementations) { - var overrides = await SymbolFinder.FindOverridesAsync(implementation, _workspace.CurrentSolution); - quickFixes.AddRange(overrides, _workspace); + quickFixes.Add(implementation, _workspace); + + if (implementation.IsOverridable()) + { + var overrides = await SymbolFinder.FindOverridesAsync(implementation, _workspace.CurrentSolution); + quickFixes.AddRange(overrides, _workspace); + } } } - - // for types also include derived classes - // for other symbols, find overrides and include those - if (symbol is INamedTypeSymbol namedTypeSymbol) + else if (symbol is INamedTypeSymbol namedTypeSymbol) { + // for types also include derived classes + // for other symbols, find overrides and include those var derivedTypes = await SymbolFinder.FindDerivedClassesAsync(namedTypeSymbol, _workspace.CurrentSolution); quickFixes.AddRange(derivedTypes, _workspace); } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs index 7b6229b620..9a362910ee 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs @@ -68,6 +68,11 @@ public override async Task Handle(RunCodeActionRequest re changes.AddRange(fileChangesResult.FileChanges); solution = fileChangesResult.Solution; } + else + { + o.Apply(this.Workspace, CancellationToken.None); + solution = this.Workspace.CurrentSolution; + } if (request.WantsAllCodeActionOperations) { diff --git a/src/OmniSharp.Roslyn/Extensions/SymbolExtensions.cs b/src/OmniSharp.Roslyn/Extensions/SymbolExtensions.cs index f88079657a..df584c08e1 100644 --- a/src/OmniSharp.Roslyn/Extensions/SymbolExtensions.cs +++ b/src/OmniSharp.Roslyn/Extensions/SymbolExtensions.cs @@ -297,5 +297,38 @@ internal static string GetFilePathForExternalSymbol(this ISymbol symbol, Project } private static string Folderize(string path) => string.Join("/", path.Split('.')); + + public static bool IsInterfaceType(this ISymbol symbol) => (symbol as ITypeSymbol)?.IsInterfaceType() == true; + + public static bool IsInterfaceType(this ITypeSymbol symbol) => symbol?.TypeKind == TypeKind.Interface; + + public static bool IsImplementableMember(this ISymbol symbol) + { + if (symbol?.ContainingType?.TypeKind == TypeKind.Interface) + { + if (symbol.Kind == SymbolKind.Event) + { + return true; + } + + if (symbol.Kind == SymbolKind.Property) + { + return true; + } + + if (symbol.Kind == SymbolKind.Method) + { + var methodSymbol = (IMethodSymbol)symbol; + if (methodSymbol.MethodKind == MethodKind.Ordinary || + methodSymbol.MethodKind == MethodKind.PropertyGet || + methodSymbol.MethodKind == MethodKind.PropertySet) + { + return true; + } + } + } + + return false; + } } } diff --git a/test-assets/test-projects/Net50Project/global.json b/test-assets/test-projects/Net50Project/global.json index fd901084a9..53424aca80 100644 --- a/test-assets/test-projects/Net50Project/global.json +++ b/test-assets/test-projects/Net50Project/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "5.0.100-preview.8.20417.9" + "version": "5.0.100-rc.1.20452.10" } } diff --git a/tests/OmniSharp.Cake.Tests/CompletionFacts.cs b/tests/OmniSharp.Cake.Tests/CompletionFacts.cs new file mode 100644 index 0000000000..83812f57b3 --- /dev/null +++ b/tests/OmniSharp.Cake.Tests/CompletionFacts.cs @@ -0,0 +1,234 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using OmniSharp.Cake.Services.RequestHandlers.Completion; +using OmniSharp.Models.UpdateBuffer; +using OmniSharp.Models.v1.Completion; +using TestUtility; +using Xunit; +using Xunit.Abstractions; + +namespace OmniSharp.Cake.Tests +{ + public class CompletionFacts : CakeSingleRequestHandlerTestFixture + { + private const int ImportCompletionTimeout = 1000; + private readonly ILogger _logger; + + public CompletionFacts(ITestOutputHelper testOutput) : base(testOutput) + { + _logger = LoggerFactory.CreateLogger(); + } + + protected override string EndpointName => OmniSharpEndpoints.Completion; + + [Fact] + public async Task ShouldGetCompletionFromHostObject() + { + const string input = @"TaskSe$$"; + + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy : false)) + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var fileName = Path.Combine(testProject.Directory, "build.cake"); + var completions = await FindCompletionsAsync(fileName, input, host); + + Assert.Contains("TaskSetup", completions.Items.Select(c => c.Label)); + Assert.Contains("TaskSetup", completions.Items.Select(c => c.InsertText)); + } + } + + [Fact] + public async Task ShouldGetCompletionFromDSL() + { + const string input = + @"Task(""Test"") + .Does(() => { + Inform$$ + });"; + + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy : false)) + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var fileName = Path.Combine(testProject.Directory, "build.cake"); + var completions = await FindCompletionsAsync(fileName, input, host); + + Assert.Contains("Information", completions.Items.Select(c => c.Label)); + Assert.Contains("Information", completions.Items.Select(c => c.InsertText)); + } + } + + [Fact] + public async Task ShouldResolveFromDSL() + { + const string input = + @"Task(""Test"") + .Does(() => { + Inform$$ + });"; + + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy : false)) + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var fileName = Path.Combine(testProject.Directory, "build.cake"); + var completion = (await FindCompletionsAsync(fileName, input, host)) + .Items.First(x => x.Preselect && x.InsertText == "Information"); + + var resolved = await ResolveCompletionAsync(completion, host); + + Assert.StartsWith( + "```csharp\nvoid Information(string format, params object[] args)", + resolved.Item?.Documentation); + } + } + + [Fact] + public async Task ShouldRemoveAdditionalTextEditsFromResolvedCompletions() + { + const string input = @"var regex = new Rege$$"; + + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy : false)) + using (var host = CreateOmniSharpHost(testProject.Directory, + new[] { new KeyValuePair("RoslynExtensionsOptions:EnableImportCompletion", "true") })) + { + var fileName = Path.Combine(testProject.Directory, "build.cake"); + + // First completion request should kick off the task to update the completion cache. + var completions = await FindCompletionsAsync(fileName, input, host); + Assert.True(completions.IsIncomplete); + Assert.DoesNotContain("Regex", completions.Items.Select(c => c.InsertText)); + + // Populating the completion cache should take no more than a few ms, don't let it take too + // long + var cts = new CancellationTokenSource(millisecondsDelay: ImportCompletionTimeout); + await Task.Run(async () => + { + while (completions.IsIncomplete) + { + completions = await FindCompletionsAsync(fileName, input, host); + cts.Token.ThrowIfCancellationRequested(); + } + }, cts.Token); + + Assert.False(completions.IsIncomplete); + Assert.Contains("Regex", completions.Items.Select(c => c.InsertText)); + + var completion = completions.Items.First(c => c.InsertText == "Regex"); + var resolved = await ResolveCompletionAsync(completion, host); + + // Due to the fact that AdditionalTextEdits return the complete buffer, we can't currently use that in Cake. + // Revisit when we have a solution. At this point it's probably just best to remove AdditionalTextEdits. + Assert.Null(resolved.Item.AdditionalTextEdits); + } + } + + [Fact] + public async Task ShouldGetAdditionalTextEditsFromOverrideCompletion() + { + const string source = @" +class Foo +{ + public virtual void Test(string text) {} + public virtual void Test(string text, string moreText) {} +} + +class FooChild : Foo +{ + override $$ +} +"; + + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy : false)) + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var fileName = Path.Combine(testProject.Directory, "build.cake"); + var completions = await FindCompletionsAsync(fileName, source, host); + Assert.Equal( + new[] + { + "Equals(object obj)", "GetHashCode()", "Test(string text)", + "Test(string text, string moreText)", "ToString()" + }, + completions.Items.Select(c => c.Label)); + Assert.Equal(new[] + { + "Equals(object obj)\n {\n return base.Equals(obj);$0\n \\}", + "GetHashCode()\n {\n return base.GetHashCode();$0\n \\}", + "Test(string text)\n {\n base.Test(text);$0\n \\}", + "Test(string text, string moreText)\n {\n base.Test(text, moreText);$0\n \\}", + "ToString()\n {\n return base.ToString();$0\n \\}" + }, + completions.Items.Select(c => c.InsertText)); + + Assert.Equal(new[] + { + "public override bool", + "public override int", + "public override void", + "public override void", + "public override string" + }, + completions.Items.Select(c => c.AdditionalTextEdits.Single().NewText)); + + Assert.All(completions.Items.Select(c => c.AdditionalTextEdits.Single()), + r => + { + Assert.Equal(9, r.StartLine); + Assert.Equal(4, r.StartColumn); + Assert.Equal(9, r.EndLine); + Assert.Equal(12, r.EndColumn); + }); + + Assert.All(completions.Items, c => Assert.Equal(InsertTextFormat.Snippet, c.InsertTextFormat)); + } + } + + private async Task FindCompletionsAsync(string filename, string source, OmniSharpTestHost host, char? triggerChar = null, TestFile[] additionalFiles = null) + { + var testFile = new TestFile(filename, source); + + var files = new[] { testFile }; + if (additionalFiles is object) + { + files = files.Concat(additionalFiles).ToArray(); + } + + host.AddFilesToWorkspace(files); + var point = testFile.Content.GetPointFromPosition(); + + var request = new CompletionRequest + { + Line = point.Line, + Column = point.Offset, + FileName = testFile.FileName, + Buffer = testFile.Content.Code, + CompletionTrigger = triggerChar is object ? CompletionTriggerKind.TriggerCharacter : CompletionTriggerKind.Invoked, + TriggerCharacter = triggerChar + }; + + var updateBufferRequest = new UpdateBufferRequest + { + Buffer = request.Buffer, + Column = request.Column, + FileName = request.FileName, + Line = request.Line, + FromDisk = false + }; + + await GetUpdateBufferHandler(host).Handle(updateBufferRequest); + + var requestHandler = GetRequestHandler(host); + + return await requestHandler.Handle(request); + } + + private static async Task ResolveCompletionAsync(CompletionItem completionItem, OmniSharpTestHost testHost) + => await GetResolveHandler(testHost).Handle(new CompletionResolveRequest { Item = completionItem }); + + private static CompletionResolveHandler GetResolveHandler(OmniSharpTestHost host) + => host.GetRequestHandler(OmniSharpEndpoints.CompletionResolve, Constants.LanguageNames.Cake); + } +} diff --git a/tests/OmniSharp.Cake.Tests/QuickInfoFacts.cs b/tests/OmniSharp.Cake.Tests/QuickInfoFacts.cs new file mode 100644 index 0000000000..0c5ac6040a --- /dev/null +++ b/tests/OmniSharp.Cake.Tests/QuickInfoFacts.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using OmniSharp.Cake.Services.RequestHandlers; +using OmniSharp.Cake.Services.RequestHandlers.Completion; +using OmniSharp.Models; +using OmniSharp.Models.UpdateBuffer; +using OmniSharp.Models.v1.Completion; +using TestUtility; +using Xunit; +using Xunit.Abstractions; + +namespace OmniSharp.Cake.Tests +{ + public class QuickInfoFacts : CakeSingleRequestHandlerTestFixture + { + private readonly ILogger _logger; + + public QuickInfoFacts(ITestOutputHelper testOutput) : base(testOutput) + { + _logger = LoggerFactory.CreateLogger(); + } + + protected override string EndpointName => OmniSharpEndpoints.QuickInfo; + + [Fact] + public async Task ShouldGetQuickInfo() + { + const string input = "Informa$$tion(\"Hello\");"; + + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy : false)) + using (var host = CreateOmniSharpHost(testProject.Directory)) + { + var fileName = Path.Combine(testProject.Directory, "build.cake"); + var quickInfo = await GetQuickInfo(fileName, input, host); + + Assert.StartsWith("```csharp\nvoid Information(string value)", quickInfo.Markdown); + } + } + + private async Task GetQuickInfo(string filename, string source, OmniSharpTestHost host, char? triggerChar = null, TestFile[] additionalFiles = null) + { + var testFile = new TestFile(filename, source); + + var files = new[] { testFile }; + if (additionalFiles is object) + { + files = files.Concat(additionalFiles).ToArray(); + } + + host.AddFilesToWorkspace(files); + var point = testFile.Content.GetPointFromPosition(); + + var request = new QuickInfoRequest + { + Line = point.Line, + Column = point.Offset, + FileName = testFile.FileName, + Buffer = testFile.Content.Code + }; + + var updateBufferRequest = new UpdateBufferRequest + { + Buffer = request.Buffer, + Column = request.Column, + FileName = request.FileName, + Line = request.Line, + FromDisk = false + }; + + await GetUpdateBufferHandler(host).Handle(updateBufferRequest); + + var requestHandler = GetRequestHandler(host); + + return await requestHandler.Handle(request); + } + } +} diff --git a/tests/OmniSharp.MSBuild.Tests/ProjectLoadListenerTests.cs b/tests/OmniSharp.MSBuild.Tests/ProjectLoadListenerTests.cs index 9735ac301c..68f3a2c612 100644 --- a/tests/OmniSharp.MSBuild.Tests/ProjectLoadListenerTests.cs +++ b/tests/OmniSharp.MSBuild.Tests/ProjectLoadListenerTests.cs @@ -1,16 +1,8 @@ using Microsoft.Build.Construction; using Microsoft.Build.Execution; using Microsoft.CodeAnalysis; -using Microsoft.Extensions.Logging; -using OmniSharp.Mef; -using OmniSharp.Models; -using OmniSharp.Models.Events; -using OmniSharp.MSBuild.Notification; using OmniSharp.Services; -using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Composition.Hosting.Core; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -98,12 +90,10 @@ public async Task The_target_framework_is_emitted() var expectedTFM = "netcoreapp2.1"; var emitter = new ProjectLoadTestEventEmitter(); - using (var testProject = await TestAssets.Instance.GetTestProjectAsync("HelloWorld")) - using (var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory))) - { - Assert.Single(emitter.ReceivedMessages); - Assert.Equal(emitter.ReceivedMessages[0].TargetFrameworks.First(), expectedTFM); - } + using var testProject = await TestAssets.Instance.GetTestProjectAsync("HelloWorld"); + using var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory)); + Assert.Single(emitter.ReceivedMessages); + Assert.Equal(emitter.ReceivedMessages[0].TargetFrameworks.First(), expectedTFM); } [Fact] @@ -112,13 +102,11 @@ public async Task If_there_is_a_solution_file_the_project_guid_present_in_it_is_ // Arrange var emitter = new ProjectLoadTestEventEmitter(); - using (var testProject = await TestAssets.Instance.GetTestProjectAsync("ProjectAndSolution")) - using (var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory))) - { - var expectedGuid = "A4C2694D-AEB4-4CB1-8951-5290424EF883".ToLower(); - Assert.Single(emitter.ReceivedMessages); - Assert.Equal(emitter.ReceivedMessages[0].ProjectId, expectedGuid); - } + using var testProject = await TestAssets.Instance.GetTestProjectAsync("ProjectAndSolution"); + using var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory)); + var expectedGuid = "A4C2694D-AEB4-4CB1-8951-5290424EF883".ToLower(); + Assert.Single(emitter.ReceivedMessages); + Assert.Equal(emitter.ReceivedMessages[0].ProjectId, expectedGuid); } [Fact] @@ -127,14 +115,12 @@ public async Task If_there_is_no_solution_file_the_hash_of_project_file_content_ // Arrange var emitter = new ProjectLoadTestEventEmitter(); - using (var testProject = await TestAssets.Instance.GetTestProjectAsync("HelloWorld")) - using (var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory))) - { - var projectFileContent = File.ReadAllText(Directory.GetFiles(testProject.Directory, "*.csproj").Single()); - var expectedGuid = GetHashedReference($"Filename: HelloWorld.csproj\n{projectFileContent}"); - Assert.Single(emitter.ReceivedMessages); - Assert.Equal(emitter.ReceivedMessages[0].ProjectId, expectedGuid); - } + using var testProject = await TestAssets.Instance.GetTestProjectAsync("HelloWorld"); + using var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory)); + var projectFileContent = File.ReadAllText(Directory.GetFiles(testProject.Directory, "*.csproj").Single()); + var expectedGuid = GetHashedReference($"Filename: HelloWorld.csproj\n{projectFileContent}"); + Assert.Single(emitter.ReceivedMessages); + Assert.Equal(emitter.ReceivedMessages[0].ProjectId, expectedGuid); } [Fact] @@ -142,16 +128,12 @@ public async Task Given_a_restored_project_the_references_are_emitted() { var emitter = new ProjectLoadTestEventEmitter(); - using (var testProject = await TestAssets.Instance.GetTestProjectAsync("HelloWorld")) - { - var dotnetCliService = new DotNetCliService(LoggerFactory, emitter); - await dotnetCliService.RestoreAsync(testProject.Directory); - using (var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory))) - { - Assert.Single(emitter.ReceivedMessages); - Assert.NotEmpty(emitter.ReceivedMessages[0].References.Where(reference => reference == GetHashedReference("system.core"))); - } - } + using var testProject = await TestAssets.Instance.GetTestProjectAsync("HelloWorld"); + var dotnetCliService = new DotNetCliService(LoggerFactory, emitter); + await dotnetCliService.RestoreAsync(testProject.Directory); + using var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory)); + Assert.Single(emitter.ReceivedMessages); + Assert.NotEmpty(emitter.ReceivedMessages[0].References.Where(reference => reference == GetHashedReference("system.core"))); } @@ -160,15 +142,13 @@ public async Task If_there_are_multiple_target_frameworks_they_are_emitted() { var emitter = new ProjectLoadTestEventEmitter(); - using (var testProject = await TestAssets.Instance.GetTestProjectAsync("ProjectWithMultiTFMLib/Lib")) - using (var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory))) - { - Assert.Single(emitter.ReceivedMessages); - var tfm = emitter.ReceivedMessages[0].TargetFrameworks.ToArray(); - Assert.Equal(2, tfm.Count()); - Assert.Equal("netstandard1.3", tfm[0]); - Assert.Equal("netstandard2.0", tfm[1]); - } + using var testProject = await TestAssets.Instance.GetTestProjectAsync("ProjectWithMultiTFMLib/Lib"); + using var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory)); + Assert.Single(emitter.ReceivedMessages); + var tfm = emitter.ReceivedMessages[0].TargetFrameworks.ToArray(); + Assert.Equal(2, tfm.Count()); + Assert.Equal("netstandard1.3", tfm[0]); + Assert.Equal("netstandard2.0", tfm[1]); } [Fact] @@ -177,13 +157,11 @@ public async Task The_hashed_references_of_the_source_files_are_emitted() // Arrange var emitter = new ProjectLoadTestEventEmitter(); - using (var testProject = await TestAssets.Instance.GetTestProjectAsync("HelloWorld")) - using (var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory))) - { - Assert.Single(emitter.ReceivedMessages); - Assert.Single(emitter.ReceivedMessages[0].FileExtensions); - Assert.Equal(emitter.ReceivedMessages[0].FileExtensions.First(), GetHashedFileExtension(".cs")); - } + using var testProject = await TestAssets.Instance.GetTestProjectAsync("HelloWorld"); + using var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory)); + Assert.Single(emitter.ReceivedMessages); + Assert.Single(emitter.ReceivedMessages[0].FileExtensions); + Assert.Equal(emitter.ReceivedMessages[0].FileExtensions.First(), GetHashedFileExtension(".cs")); } [Fact] @@ -192,12 +170,10 @@ public async Task The_output_kind_is_emitted() // Arrange var emitter = new ProjectLoadTestEventEmitter(); - using (var testProject = await TestAssets.Instance.GetTestProjectAsync("NetCore31Project")) - using (var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory))) - { - Assert.Single(emitter.ReceivedMessages); - Assert.Equal((int)OutputKind.ConsoleApplication, emitter.ReceivedMessages[0].OutputKind); - } + using var testProject = await TestAssets.Instance.GetTestProjectAsync("NetCore31Project"); + using var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory)); + Assert.Single(emitter.ReceivedMessages); + Assert.Equal((int)OutputKind.ConsoleApplication, emitter.ReceivedMessages[0].OutputKind); } [Fact] @@ -206,12 +182,10 @@ public async Task The_correct_project_capablities_is_emitted() // Arrange var emitter = new ProjectLoadTestEventEmitter(); - using (var testProject = await TestAssets.Instance.GetTestProjectAsync("NetCore31Project")) - using (var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory))) - { - Assert.Single(emitter.ReceivedMessages); - Assert.Equal("GenerateDocumentationFile CSharp Managed ReferencesFolder LanguageService RelativePathDerivedDefaultNamespace AssemblyReferences COMReferences ProjectReferences SharedProjectReferences OutputGroups AllTargetOutputGroups VisualStudioWellKnownOutputGroups SingleFileGenerators DeclaredSourceItems UserSourceItems BuildWindowsDesktopTarget CrossPlatformExecutable Pack", string.Join(" ", emitter.ReceivedMessages[0].ProjectCapabilities)); - } + using var testProject = await TestAssets.Instance.GetTestProjectAsync("NetCore31Project"); + using var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory)); + Assert.Single(emitter.ReceivedMessages); + Assert.Equal("GenerateDocumentationFile CSharp Managed ReferencesFolder LanguageService RelativePathDerivedDefaultNamespace AssemblyReferences COMReferences ProjectReferences SharedProjectReferences OutputGroups AllTargetOutputGroups VisualStudioWellKnownOutputGroups SingleFileGenerators DeclaredSourceItems UserSourceItems BuildWindowsDesktopTarget CrossPlatformExecutable Pack", string.Join(" ", emitter.ReceivedMessages[0].ProjectCapabilities)); } [Fact] @@ -220,12 +194,10 @@ public async Task The_correct_sdk_version_is_emitted() // Arrange var emitter = new ProjectLoadTestEventEmitter(); - using (var testProject = await TestAssets.Instance.GetTestProjectAsync("NetCore31Project")) - using (var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory))) - { - Assert.Single(emitter.ReceivedMessages); - Assert.Equal(GetHashedFileExtension("3.1.401"), emitter.ReceivedMessages[0].SdkVersion); - } + using var testProject = await TestAssets.Instance.GetTestProjectAsync("NetCore31Project"); + using var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory)); + Assert.Single(emitter.ReceivedMessages); + Assert.Equal(GetHashedFileExtension("3.1.401"), emitter.ReceivedMessages[0].SdkVersion); } [Fact] @@ -234,12 +206,10 @@ public async Task The_correct_sdk_version_is_emitted_2() // Arrange var emitter = new ProjectLoadTestEventEmitter(); - using (var testProject = await TestAssets.Instance.GetTestProjectAsync("Net50Project")) - using (var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory))) - { - Assert.Equal(2, emitter.ReceivedMessages.Length); - Assert.Equal(GetHashedFileExtension("5.0.100-preview.8.20417.9"), emitter.ReceivedMessages[0].SdkVersion); - } + using var testProject = await TestAssets.Instance.GetTestProjectAsync("Net50Project"); + using var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory)); + Assert.Equal(2, emitter.ReceivedMessages.Length); + Assert.Equal(GetHashedFileExtension("5.0.100-rc.1.20452.10"), emitter.ReceivedMessages[0].SdkVersion); } private string GetHashedFileExtension(string fileExtension) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CompletionFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CompletionFacts.cs index 917fb0dec2..56b8f607bd 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CompletionFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CompletionFacts.cs @@ -1336,6 +1336,48 @@ public async Task InternalsVisibleToCompletionSkipsMiscProject() Assert.Equal("AssemblyNameVal", completions.Items[0].InsertText); } + [ConditionalTheory(typeof(WindowsOnly))] + [InlineData("dummy.cs")] + [InlineData("dummy.csx")] + public async Task RegexCompletionInNormalString(string filename) + { + const string input = @" +using System.Text.RegularExpressions; +class Foo +{ + public void M() + { + _ = new Regex(""$$""); + } +}"; + + var completions = await FindCompletionsAsync(filename, input, SharedOmniSharpTestHost); + var aCompletion = completions.Items.First(c => c.Label == @"\A"); + Assert.NotNull(aCompletion); + Assert.Equal(@"\\A", aCompletion.InsertText); + } + + [ConditionalTheory(typeof(WindowsOnly))] + [InlineData("dummy.cs")] + [InlineData("dummy.csx")] + public async Task RegexCompletionInVerbatimString(string filename) + { + const string input = @" +using System.Text.RegularExpressions; +class Foo +{ + public void M() + { + _ = new Regex(@""$$""); + } +}"; + + var completions = await FindCompletionsAsync(filename, input, SharedOmniSharpTestHost); + var aCompletion = completions.Items.First(c => c.Label == @"\A"); + Assert.NotNull(aCompletion); + Assert.Equal(@"\A", aCompletion.InsertText); + } + private CompletionService GetCompletionService(OmniSharpTestHost host) => host.GetRequestHandler(EndpointName); diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/IntellisenseFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/IntellisenseFacts.cs index d53baa0c6f..909d7c9895 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/IntellisenseFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/IntellisenseFacts.cs @@ -475,6 +475,33 @@ void M() Assert.Contains(@"The ""G"" standard format specifier", gStandardCompletion.Description); } + [ConditionalTheory(typeof(WindowsOnly))] + [InlineData("dummy.cs")] + [InlineData("dummy.csx")] + public async Task Embedded_language_completion_provider_for_regex(string filename) + { + const string source = @" + using System; + using System.Text.RegularExpressions; + class C + { + void M() + { + var r = Regex.Match(""foo"", ""$$"" + } + } + "; + + var completions = await FindCompletionsAsync(filename, source); + + Assert.NotEmpty(completions); + + var wCompletion = completions.FirstOrDefault(x => x.CompletionText == @"\w"); + Assert.NotNull(wCompletion); + Assert.Equal("word character", wCompletion.DisplayText); + Assert.Contains(@"matches any word character", wCompletion.Description); + } + [Fact] public async Task Scripting_by_default_returns_completions_for_CSharp7_1() { diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/OmniSharp.Roslyn.CSharp.Tests.csproj b/tests/OmniSharp.Roslyn.CSharp.Tests/OmniSharp.Roslyn.CSharp.Tests.csproj index b62c699c6e..fbddead4eb 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/OmniSharp.Roslyn.CSharp.Tests.csproj +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/OmniSharp.Roslyn.CSharp.Tests.csproj @@ -4,6 +4,7 @@ net472 AnyCPU true + CS0618 diff --git a/tools/packages.config b/tools/packages.config index 49c9d0c1b7..3108240628 100644 --- a/tools/packages.config +++ b/tools/packages.config @@ -2,32 +2,32 @@ - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + +