diff --git a/build/Packages.props b/build/Packages.props index 029f591b78..b0f753faac 100644 --- a/build/Packages.props +++ b/build/Packages.props @@ -4,7 +4,7 @@ 16.0.461 5.0.0 - 3.2.0-beta4-19326-12 + 3.3.0-beta2-19376-02 2.4.0 diff --git a/src/OmniSharp.Abstractions/Configuration.cs b/src/OmniSharp.Abstractions/Configuration.cs index 7e0f2eab0c..56664e7a37 100644 --- a/src/OmniSharp.Abstractions/Configuration.cs +++ b/src/OmniSharp.Abstractions/Configuration.cs @@ -4,7 +4,7 @@ internal static class Configuration { public static bool ZeroBasedIndices = false; - public const string RoslynVersion = "3.2.0.0"; + public const string RoslynVersion = "3.3.0.0"; public const string RoslynPublicKeyToken = "31bf3856ad364e35"; public readonly static string RoslynFeatures = GetRoslynAssemblyFullName("Microsoft.CodeAnalysis.Features"); diff --git a/src/OmniSharp.Http.Driver/app.config b/src/OmniSharp.Http.Driver/app.config index ff336a2a60..aedd66b2a7 100644 --- a/src/OmniSharp.Http.Driver/app.config +++ b/src/OmniSharp.Http.Driver/app.config @@ -7,15 +7,15 @@ - + - + - + diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs index 585622530c..1920cb64a1 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs @@ -51,6 +51,7 @@ private class ProjectData public RuleSet RuleSet { get; } public ImmutableDictionary ReferenceAliases { get; } public bool TreatWarningsAsErrors { get; } + public string DefaultNamespace { get; } private ProjectData() { @@ -85,6 +86,7 @@ private ProjectData( bool signAssembly, string assemblyOriginatorKeyFile, bool treatWarningsAsErrors, + string defaultNamespace, RuleSet ruleset) : this() { @@ -114,6 +116,7 @@ private ProjectData( AssemblyOriginatorKeyFile = assemblyOriginatorKeyFile; TreatWarningsAsErrors = treatWarningsAsErrors; RuleSet = ruleset; + DefaultNamespace = defaultNamespace; } private ProjectData( @@ -139,11 +142,12 @@ private ProjectData( ImmutableArray analyzers, ImmutableArray additionalFiles, bool treatWarningsAsErrors, + string defaultNamespace, RuleSet ruleset, ImmutableDictionary referenceAliases) : this(guid, name, assemblyName, targetPath, outputPath, intermediateOutputPath, projectAssetsFile, configuration, platform, targetFramework, targetFrameworks, outputKind, languageVersion, nullableContextOptions, allowUnsafeCode, - documentationFile, preprocessorSymbolNames, suppressedDiagnosticIds, signAssembly, assemblyOriginatorKeyFile, treatWarningsAsErrors, ruleset) + documentationFile, preprocessorSymbolNames, suppressedDiagnosticIds, signAssembly, assemblyOriginatorKeyFile, treatWarningsAsErrors, defaultNamespace, ruleset) { SourceFiles = sourceFiles.EmptyIfDefault(); ProjectReferences = projectReferences.EmptyIfDefault(); @@ -165,6 +169,7 @@ public static ProjectData Create(MSB.Evaluation.Project project) var projectAssetsFile = project.GetPropertyValue(PropertyNames.ProjectAssetsFile); var configuration = project.GetPropertyValue(PropertyNames.Configuration); var platform = project.GetPropertyValue(PropertyNames.Platform); + var defaultNamespace = project.GetPropertyValue(PropertyNames.RootNamespace); var targetFramework = new FrameworkName(project.GetPropertyValue(PropertyNames.TargetFrameworkMoniker)); @@ -190,7 +195,7 @@ public static ProjectData Create(MSB.Evaluation.Project project) return new ProjectData( guid, name, assemblyName, targetPath, outputPath, intermediateOutputPath, projectAssetsFile, configuration, platform, targetFramework, targetFrameworks, outputKind, languageVersion, nullableContextOptions, allowUnsafeCode, - documentationFile, preprocessorSymbolNames, suppressedDiagnosticIds, signAssembly, assemblyOriginatorKeyFile, treatWarningsAsErrors, ruleset: null); + documentationFile, preprocessorSymbolNames, suppressedDiagnosticIds, signAssembly, assemblyOriginatorKeyFile, treatWarningsAsErrors, defaultNamespace, ruleset: null); } public static ProjectData Create(MSB.Execution.ProjectInstance projectInstance) @@ -204,6 +209,7 @@ public static ProjectData Create(MSB.Execution.ProjectInstance projectInstance) var projectAssetsFile = projectInstance.GetPropertyValue(PropertyNames.ProjectAssetsFile); var configuration = projectInstance.GetPropertyValue(PropertyNames.Configuration); var platform = projectInstance.GetPropertyValue(PropertyNames.Platform); + var defaultNamespace = projectInstance.GetPropertyValue(PropertyNames.RootNamespace); var targetFramework = new FrameworkName(projectInstance.GetPropertyValue(PropertyNames.TargetFrameworkMoniker)); @@ -277,7 +283,7 @@ public static ProjectData Create(MSB.Execution.ProjectInstance projectInstance) configuration, platform, targetFramework, targetFrameworks, outputKind, languageVersion, nullableContextOptions, allowUnsafeCode, documentationFile, preprocessorSymbolNames, suppressedDiagnosticIds, signAssembly, assemblyOriginatorKeyFile, - sourceFiles, projectReferences, references.ToImmutable(), packageReferences, analyzers, additionalFiles, treatWarningsAsErrors, ruleset, referenceAliases.ToImmutableDictionary()); + sourceFiles, projectReferences, references.ToImmutable(), packageReferences, analyzers, additionalFiles, treatWarningsAsErrors, defaultNamespace, ruleset, referenceAliases.ToImmutableDictionary()); } private static RuleSet ResolveRulesetIfAny(MSB.Execution.ProjectInstance projectInstance) diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs index 1e9af6008f..2a2d81221d 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs @@ -53,6 +53,8 @@ internal partial class ProjectFileInfo public ImmutableArray AdditionalFiles => _data.AdditionalFiles; public ImmutableDictionary ReferenceAliases => _data.ReferenceAliases; public bool TreatWarningsAsErrors => _data.TreatWarningsAsErrors; + public string DefaultNamespace => _data.DefaultNamespace; + public ProjectIdInfo ProjectIdInfo { get; } private ProjectFileInfo( diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs index fd61c17442..f6a87674ed 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs @@ -52,7 +52,6 @@ public static CSharpCompilationOptions CreateCompilationOptions(this ProjectFile public static ImmutableDictionary GetDiagnosticOptions(this ProjectFileInfo projectFileInfo) { var defaultSuppressions = CompilationOptionsHelper.GetDefaultSuppressedDiagnosticOptions(projectFileInfo.SuppressedDiagnosticIds); - var specificRules = projectFileInfo.RuleSet?.SpecificDiagnosticOptions ?? ImmutableDictionary.Empty; return specificRules.Concat(defaultSuppressions.Where(x => !specificRules.Keys.Contains(x.Key))).ToImmutableDictionary(); @@ -71,7 +70,7 @@ public static ProjectInfo CreateProjectInfo(this ProjectFileInfo projectFileInfo filePath: projectFileInfo.FilePath, outputFilePath: projectFileInfo.TargetPath, compilationOptions: projectFileInfo.CreateCompilationOptions(), - analyzerReferences: analyzerReferences); + analyzerReferences: analyzerReferences).WithDefaultNamespace(projectFileInfo.DefaultNamespace); } private static IEnumerable ResolveAnalyzerReferencesForProject(ProjectFileInfo projectFileInfo, IAnalyzerAssemblyLoader analyzerAssemblyLoader) diff --git a/src/OmniSharp.MSBuild/ProjectFile/PropertyNames.cs b/src/OmniSharp.MSBuild/ProjectFile/PropertyNames.cs index 4bbf8076e2..5178510759 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/PropertyNames.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/PropertyNames.cs @@ -29,6 +29,7 @@ internal static class PropertyNames public const string ProjectGuid = nameof(ProjectGuid); public const string ProjectName = nameof(ProjectName); public const string _ResolveReferenceDependencies = nameof(_ResolveReferenceDependencies); + public const string RootNamespace = nameof(RootNamespace); public const string RoslynTargetsPath = nameof(RoslynTargetsPath); public const string SignAssembly = nameof(SignAssembly); public const string SkipCompilerExecution = nameof(SkipCompilerExecution); diff --git a/src/OmniSharp.MSBuild/ProjectManager.cs b/src/OmniSharp.MSBuild/ProjectManager.cs index 068d98cd2c..f0ff0e0f4b 100644 --- a/src/OmniSharp.MSBuild/ProjectManager.cs +++ b/src/OmniSharp.MSBuild/ProjectManager.cs @@ -434,14 +434,15 @@ private void UpdateProject(string projectFilePath) UpdateParseOptions(project, projectFileInfo.LanguageVersion, projectFileInfo.PreprocessorSymbolNames, !string.IsNullOrWhiteSpace(projectFileInfo.DocumentationFile)); UpdateProjectReferences(project, projectFileInfo.ProjectReferences); UpdateReferences(project, projectFileInfo.ProjectReferences, projectFileInfo.References); - UpdateAnalyzerReferences(projectFileInfo, project); + UpdateAnalyzerReferences(project, projectFileInfo); UpdateAdditionalFiles(project, projectFileInfo.AdditionalFiles); + UpdateProjectProperties(project, projectFileInfo); _workspace.TryPromoteMiscellaneousDocumentsToProject(project); _workspace.UpdateDiagnosticOptionsForProject(project.Id, projectFileInfo.GetDiagnosticOptions()); } - private void UpdateAnalyzerReferences(ProjectFileInfo projectFileInfo, Project project) + private void UpdateAnalyzerReferences(Project project, ProjectFileInfo projectFileInfo) { var analyzerFileReferences = projectFileInfo.Analyzers .Select(analyzerReferencePath => new AnalyzerFileReference(analyzerReferencePath, _analyzerAssemblyLoader)) @@ -450,6 +451,22 @@ private void UpdateAnalyzerReferences(ProjectFileInfo projectFileInfo, Project p _workspace.SetAnalyzerReferences(project.Id, analyzerFileReferences); } + private void UpdateProjectProperties(Project project, ProjectFileInfo projectFileInfo) + { + if (projectFileInfo.DefaultNamespace != project.DefaultNamespace) + { + var newSolution = _workspace.CurrentSolution.WithProjectDefaultNamespace(project.Id, projectFileInfo.DefaultNamespace); + if (_workspace.TryApplyChanges(newSolution)) + { + _logger.LogDebug($"Updated default namespace from {project.DefaultNamespace} to {projectFileInfo.DefaultNamespace} on {project.FilePath} project."); + } + else + { + _logger.LogWarning($"Couldn't update default namespace from {project.DefaultNamespace} to {projectFileInfo.DefaultNamespace} on {project.FilePath} project."); + } + } + } + private void UpdateAdditionalFiles(Project project, IList additionalFiles) { var currentAdditionalDocuments = project.AdditionalDocuments; @@ -490,7 +507,7 @@ private void UpdateSourceFiles(Project project, IList sourceFiles) continue; } - _workspace.AddDocument(project.Id, sourceFile); + _workspace.AddDocument(project, sourceFile); } // Removing any remaining documents from the project. diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs index af1d9aefa2..c7dc00d9a0 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs @@ -148,7 +148,7 @@ public override async Task Handle(RunCodeActionRequest re } } - this.Workspace.AddDocument(documentId, projectChange.ProjectId, newFilePath, newDocument.SourceCodeKind); + this.Workspace.AddDocument(documentId, projectChange.NewProject, newFilePath, newDocument.SourceCodeKind); solution = this.Workspace.CurrentSolution; } else diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Signatures/SignatureHelpService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Signatures/SignatureHelpService.cs index 03a8137d57..ad776a896f 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Signatures/SignatureHelpService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Signatures/SignatureHelpService.cs @@ -77,7 +77,7 @@ throughExpression is LiteralExpressionSyntax || } else if (invocation.Receiver is SimpleNameSyntax && invocation.IsInStaticContext) { - methodGroup = methodGroup.Where(m => m.IsStatic); + methodGroup = methodGroup.Where(m => m.IsStatic || m.MethodKind == MethodKind.LocalFunction); } foreach (var methodOverload in methodGroup) diff --git a/src/OmniSharp.Roslyn/BufferManager.cs b/src/OmniSharp.Roslyn/BufferManager.cs index c40b5deaa1..c53c821387 100644 --- a/src/OmniSharp.Roslyn/BufferManager.cs +++ b/src/OmniSharp.Roslyn/BufferManager.cs @@ -151,29 +151,26 @@ private bool TryAddTransientDocument(string fileName, string fileContent) } else { + var projectAndDocumentIds = new List<(ProjectId ProjectId, DocumentId DocumentId)>(); var sourceText = SourceText.From(fileContent); - var documentInfos = new List(); + foreach (var project in projects) { - var id = DocumentId.CreateNewId(project.Id); - var version = VersionStamp.Create(); - var documentInfo = DocumentInfo.Create( - id, fileName, filePath: fileName, - loader: TextLoader.From(TextAndVersion.Create(sourceText, version))); - - documentInfos.Add(documentInfo); + var documentId = DocumentId.CreateNewId(project.Id); + projectAndDocumentIds.Add((project.Id, documentId)); } lock (_lock) { - var documentIds = documentInfos.Select(document => document.Id); + var documentIds = projectAndDocumentIds.Select(x => x.DocumentId); _transientDocuments[fileName] = documentIds; _transientDocumentIds.UnionWith(documentIds); } - foreach (var documentInfo in documentInfos) + foreach (var projectAndDocumentId in projectAndDocumentIds) { - _workspace.AddDocument(documentInfo); + var version = VersionStamp.Create(); + _workspace.AddDocument(projectAndDocumentId.DocumentId, projectAndDocumentId.ProjectId, fileName, TextLoader.From(TextAndVersion.Create(sourceText, version))); } } diff --git a/src/OmniSharp.Roslyn/MetadataHelper.cs b/src/OmniSharp.Roslyn/MetadataHelper.cs index d1bbb81d92..e8f03f03a7 100644 --- a/src/OmniSharp.Roslyn/MetadataHelper.cs +++ b/src/OmniSharp.Roslyn/MetadataHelper.cs @@ -106,7 +106,7 @@ public string GetSymbolName(ISymbol symbol) public async Task GetSymbolLocationFromMetadata(ISymbol symbol, Document metadataDocument, CancellationToken cancellationToken = new CancellationToken()) { - var symbolKeyCreateMethod = _symbolKey.GetMethod(Create, BindingFlags.Static | BindingFlags.Public); + var symbolKeyCreateMethod = _symbolKey.GetMethod(Create, BindingFlags.Static | BindingFlags.NonPublic); var symboldId = symbolKeyCreateMethod.InvokeStatic(new object[] { symbol, cancellationToken }); return await _getLocationInGeneratedSourceAsync.InvokeStatic>(new object[] { symboldId, metadataDocument, cancellationToken }); diff --git a/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs b/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs index b31560ff7e..d8213e92a6 100644 --- a/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs +++ b/src/OmniSharp.Roslyn/OmniSharpWorkspace.cs @@ -5,6 +5,7 @@ using System.Composition; using System.IO; using System.Linq; +using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -12,6 +13,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; using Microsoft.Extensions.Logging; +using OmniSharp.FileSystem; using OmniSharp.FileWatching; using OmniSharp.Roslyn; using OmniSharp.Roslyn.Utilities; @@ -93,16 +95,6 @@ public void RemoveMetadataReference(ProjectId projectId, MetadataReference metad OnMetadataReferenceRemoved(projectId, metadataReference); } - public void AddDocument(DocumentInfo documentInfo) - { - // if the file has already been added as a misc file, - // because of a possible race condition between the updates of the project systems, - // remove the misc file and add the document as required - TryRemoveMiscellaneousDocument(documentInfo.FilePath); - - OnDocumentAdded(documentInfo); - } - public DocumentId TryAddMiscellaneousDocument(string filePath, string language) { if (GetDocument(filePath) != null) @@ -180,19 +172,74 @@ private ProjectInfo CreateMiscFilesProject(string language) return projectInfo; } + public void AddDocument(DocumentInfo documentInfo) + { + // if the file has already been added as a misc file, + // because of a possible race condition between the updates of the project systems, + // remove the misc file and add the document as required + TryRemoveMiscellaneousDocument(documentInfo.FilePath); + + OnDocumentAdded(documentInfo); + } + public DocumentId AddDocument(ProjectId projectId, string filePath, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular) + { + var project = this.CurrentSolution.GetProject(projectId); + return AddDocument(project, filePath, sourceCodeKind); + } + + public DocumentId AddDocument(ProjectId projectId, string filePath, TextLoader loader, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular) { var documentId = DocumentId.CreateNewId(projectId); - this.AddDocument(documentId, projectId, filePath, sourceCodeKind); - return documentId; + var project = this.CurrentSolution.GetProject(projectId); + return AddDocument(documentId, project, filePath, loader, sourceCodeKind); } - public DocumentId AddDocument(DocumentId documentId, ProjectId projectId, string filePath, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular) + public DocumentId AddDocument(Project project, string filePath, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular) { - var loader = new OmniSharpTextLoader(filePath); - var documentInfo = DocumentInfo.Create(documentId, Path.GetFileName(filePath), filePath: filePath, loader: loader, sourceCodeKind: sourceCodeKind); + var documentId = DocumentId.CreateNewId(project.Id); + return AddDocument(documentId, project, filePath, sourceCodeKind); + } - this.AddDocument(documentInfo); + public DocumentId AddDocument(DocumentId documentId, Project project, string filePath, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular) + { + return AddDocument(documentId, project, filePath, new OmniSharpTextLoader(filePath), sourceCodeKind); + } + + internal DocumentId AddDocument(DocumentId documentId, ProjectId projectId, string filePath, TextLoader loader, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular) + { + var project = this.CurrentSolution.GetProject(projectId); + return AddDocument(documentId, project, filePath, loader, sourceCodeKind); + } + + internal DocumentId AddDocument(DocumentId documentId, Project project, string filePath, TextLoader loader, SourceCodeKind sourceCodeKind = SourceCodeKind.Regular) + { + var basePath = Path.GetDirectoryName(project.FilePath); + var fullPath = Path.GetDirectoryName(filePath); + + IEnumerable folders = null; + + // folder computation is best effort. in case of exceptions, we back out because it's not essential for core features + try + { + // find the relative path from project file to our document + var relativeDocumentPath = FileSystemHelper.GetRelativePath(fullPath, basePath); + + // only set document's folders if + // 1. relative path was computed + // 2. path is not pointing any level up + if (relativeDocumentPath != null && !relativeDocumentPath.StartsWith("..")) + { + folders = relativeDocumentPath?.Split(new[] { Path.DirectorySeparatorChar }); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, $"An error occurred when computing a relative path from {basePath} to {fullPath}. Document at {filePath} will be processed without folder structure."); + } + + var documentInfo = DocumentInfo.Create(documentId, Path.GetFileName(filePath), folders: folders, filePath: filePath, loader: loader, sourceCodeKind: sourceCodeKind); + AddDocument(documentInfo); return documentId; } @@ -427,5 +474,21 @@ public void RemoveAdditionalDocument(DocumentId documentId) { OnAdditionalDocumentRemoved(documentId); } + + protected override void ApplyProjectChanges(ProjectChanges projectChanges) + { + // since Roslyn currently doesn't handle DefaultNamespace changes via ApplyProjectChanges + // and OnDefaultNamespaceChanged is internal, we use reflection for now + if (projectChanges.NewProject.DefaultNamespace != projectChanges.OldProject.DefaultNamespace) + { + var onDefaultNamespaceChanged = this.GetType().GetMethod("OnDefaultNamespaceChanged", BindingFlags.Instance | BindingFlags.NonPublic); + if (onDefaultNamespaceChanged != null) + { + onDefaultNamespaceChanged.Invoke(this, new object[] { projectChanges.ProjectId, projectChanges.NewProject.DefaultNamespace }); + } + } + + base.ApplyProjectChanges(projectChanges); + } } } diff --git a/src/OmniSharp.Shared/FileSystem/FileSystemHelper.cs b/src/OmniSharp.Shared/FileSystem/FileSystemHelper.cs index e52a0444b8..d0aabcf1db 100644 --- a/src/OmniSharp.Shared/FileSystem/FileSystemHelper.cs +++ b/src/OmniSharp.Shared/FileSystem/FileSystemHelper.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Composition; +using System.IO; using System.Linq; using Microsoft.Extensions.FileSystemGlobbing; using OmniSharp.Options; @@ -9,6 +11,7 @@ namespace OmniSharp.FileSystem [Export, Shared] public class FileSystemHelper { + private static string s_directorySeparatorChar = Path.DirectorySeparatorChar.ToString(); private readonly OmniSharpOptions _omniSharpOptions; private readonly IOmniSharpEnvironment _omniSharpEnvironment; @@ -38,5 +41,28 @@ public IEnumerable GetFiles(string includePattern, string targetDirector return matcher.GetResultsInFullPath(targetDirectory); } + + public static string GetRelativePath(string fullPath, string basePath) + { + // if any of them is not set, abort + if (string.IsNullOrWhiteSpace(basePath) || string.IsNullOrWhiteSpace(fullPath)) return null; + + // paths must be rooted + if (!Path.IsPathRooted(basePath) || !Path.IsPathRooted(fullPath)) return null; + + // if they are the same, abort + if (fullPath.Equals(basePath, StringComparison.Ordinal)) return null; + + if (!Path.HasExtension(basePath) && !basePath.EndsWith(s_directorySeparatorChar)) + { + basePath += Path.DirectorySeparatorChar; + } + + var baseUri = new Uri(basePath); + var fullUri = new Uri(fullPath); + var relativeUri = baseUri.MakeRelativeUri(fullUri); + var relativePath = Uri.UnescapeDataString(relativeUri.ToString()).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + return relativePath; + } } } diff --git a/src/OmniSharp.Stdio.Driver/app.config b/src/OmniSharp.Stdio.Driver/app.config index ff336a2a60..aedd66b2a7 100644 --- a/src/OmniSharp.Stdio.Driver/app.config +++ b/src/OmniSharp.Stdio.Driver/app.config @@ -7,15 +7,15 @@ - + - + - + diff --git a/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs b/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs index 6a8987e24e..6f8bd0a1e8 100644 --- a/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs +++ b/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs @@ -64,7 +64,8 @@ public void Whatever() { "using System.Text.RegularExpressions;", "System.Text.RegularExpressions.Regex", - "Extract Method" + "Extract Method", + "Introduce local for 'Regex.Match(\"foo\", \"bar\")'" }; Assert.Equal(expected, refactorings); } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs index 922e5ad716..d1d117784d 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs @@ -137,7 +137,8 @@ public void Whatever() "Generate type 'Console' -> Generate class 'Console' in new file", "Generate type 'Console' -> Generate class 'Console'", "Generate type 'Console' -> Generate nested class 'Console'", - "Extract Method" + "Extract Method", + "Introduce local for 'Console.Write(\"should be using System;\")'" } : new List { "using System;", @@ -150,7 +151,8 @@ public void Whatever() "Generate type 'Console' -> Generate class 'Console' in new file", "Generate type 'Console' -> Generate class 'Console'", "Generate type 'Console' -> Generate nested class 'Console'", - "Extract Method" + "Extract Method", + "Introduce local for 'Console.Write(\"should be using System;\")'" }; Assert.Equal(expected.OrderBy(x => x), refactorings.OrderBy(x => x)); } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs index 0f7fb8a351..ace7d9d098 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/FilesChangedFacts.cs @@ -32,7 +32,7 @@ public async Task TestFileAddedToMSBuildWorkspaceOnCreation() var handler = GetRequestHandler(host); await handler.Handle(new[] { new FilesChangedRequest() { FileName = filePath, ChangeType = FileChangeType.Create } }); - Assert.Contains(host.Workspace.CurrentSolution.Projects.First().Documents, d => d.Name == filePath); + Assert.Contains(host.Workspace.CurrentSolution.Projects.First().Documents, d => d.FilePath == filePath && d.Name == "FileName.cs"); } } @@ -51,7 +51,7 @@ public async Task TestFileMovedToPreviouslyEmptyDirectory() var handler = GetRequestHandler(host); await handler.Handle(new[] { new FilesChangedRequest() { FileName = filePath, ChangeType = FileChangeType.Create } }); - Assert.Contains(host.Workspace.CurrentSolution.Projects.First().Documents, d => d.Name == filePath); + Assert.Contains(host.Workspace.CurrentSolution.Projects.First().Documents, d => d.FilePath == filePath && d.Name == "FileName.cs"); var nestedDirectory = Path.Combine(projectDirectory, "Nested"); Directory.CreateDirectory(nestedDirectory); @@ -62,8 +62,8 @@ public async Task TestFileMovedToPreviouslyEmptyDirectory() await handler.Handle(new[] { new FilesChangedRequest() { FileName = filePath, ChangeType = FileChangeType.Delete } }); await handler.Handle(new[] { new FilesChangedRequest() { FileName = destinationPath, ChangeType = FileChangeType.Create } }); - Assert.Contains(host.Workspace.CurrentSolution.Projects.First().Documents, d => d.Name == destinationPath); - Assert.DoesNotContain(host.Workspace.CurrentSolution.Projects.First().Documents, d => d.Name == filePath); + Assert.Contains(host.Workspace.CurrentSolution.Projects.First().Documents, d => d.FilePath == destinationPath && d.Name == "FileName.cs"); + Assert.DoesNotContain(host.Workspace.CurrentSolution.Projects.First().Documents, d => d.FilePath == filePath && d.Name == "FileName.cs"); } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/SignatureHelpFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/SignatureHelpFacts.cs index 9d00fcdd57..a6e5e682fe 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/SignatureHelpFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/SignatureHelpFacts.cs @@ -880,7 +880,7 @@ public static void Main() [Theory] [InlineData("dummy.cs")] [InlineData("dummy.csx")] - public async Task GivesHelpForLocalFunctions(string filename) + public async Task GivesHelpForLocalFunctionsInStaticContext(string filename) { const string source = @"class Program @@ -903,6 +903,32 @@ bool LocalFunction(int i) Assert.Equal("int i", signature.Parameters.ElementAt(0).Label); } + [Theory] + [InlineData("dummy.cs")] + [InlineData("dummy.csx")] + public async Task GivesHelpForLocalFunctionsInNonStaticContext(string filename) + { + const string source = +@"class Program +{ + public void Main() + { + var flag = LocalFunction($$); + bool LocalFunction(int i) + { + return i > 0; + } + } +}"; + var actual = await GetSignatureHelp(filename, source); + Assert.Single(actual.Signatures); + + var signature = actual.Signatures.ElementAt(0); + Assert.Single(signature.Parameters); + Assert.Equal("i", signature.Parameters.ElementAt(0).Name); + Assert.Equal("int i", signature.Parameters.ElementAt(0).Label); + } + [Theory] [InlineData("dummy.cs")] [InlineData("dummy.csx")] diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/SyncNamespaceFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/SyncNamespaceFacts.cs new file mode 100644 index 0000000000..43460cf9b8 --- /dev/null +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/SyncNamespaceFacts.cs @@ -0,0 +1,165 @@ +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using OmniSharp.Models; +using OmniSharp.Models.V2.CodeActions; +using OmniSharp.Roslyn.CSharp.Services.Refactoring.V2; +using TestUtility; +using Xunit; +using Xunit.Abstractions; + +namespace OmniSharp.Roslyn.CSharp.Tests +{ + public class SyncNamespaceFacts : AbstractTestFixture + { + public SyncNamespaceFacts(ITestOutputHelper output) + : base(output) + { + } + + [Theory] + [InlineData("OmniSharpTest", "Bar.cs")] + [InlineData("OmniSharpTest.Foo", "Foo", "Bar.cs")] + [InlineData("OmniSharpTest.Foo.Bar", "Foo", "Bar", "Baz.cs")] + public async Task RespectFolderName_InOfferedRefactorings(string expectedNamespace, params string[] relativePath) + { + var testFile = new TestFile(Path.Combine(TestAssets.Instance.TestFilesFolder, Path.Combine(relativePath)), @"namespace Xx$$x { }"); + + using (var host = CreateOmniSharpHost(new[] { testFile }, null, path: TestAssets.Instance.TestFilesFolder)) + { + var point = testFile.Content.GetPointFromPosition(); + var getRequestHandler = host.GetRequestHandler(OmniSharpEndpoints.V2.GetCodeActions); + var getRequest = new GetCodeActionsRequest + { + Line = point.Line, + Column = point.Offset, + FileName = testFile.FileName + }; + + var getResponse = await getRequestHandler.Handle(getRequest); + Assert.NotNull(getResponse.CodeActions); + Assert.Contains(getResponse.CodeActions, f => f.Name == $"Change namespace to '{expectedNamespace}'"); + } + } + + [Theory] + [InlineData("LiveChanged", "Bar.cs")] + [InlineData("LiveChanged.Foo", "Foo", "Bar.cs")] + [InlineData("LiveChanged.Foo.Bar", "Foo", "Bar", "Baz.cs")] + public async Task RespectFolderName_InOfferedRefactorings_AfterLiveChange(string expectedNamespace, params string[] relativePath) + { + var testFile = new TestFile(Path.Combine(TestAssets.Instance.TestFilesFolder, Path.Combine(relativePath)), @"namespace Xx$$x { }"); + + using (var host = CreateOmniSharpHost(new[] { testFile }, null, path: TestAssets.Instance.TestFilesFolder)) + { + var point = testFile.Content.GetPointFromPosition(); + var getRequestHandler = host.GetRequestHandler(OmniSharpEndpoints.V2.GetCodeActions); + + var changedSolution = host.Workspace.CurrentSolution.WithProjectDefaultNamespace(host.Workspace.CurrentSolution.Projects.ElementAt(0).Id, "LiveChanged"); + host.Workspace.TryApplyChanges(changedSolution); + + var getRequest = new GetCodeActionsRequest + { + Line = point.Line, + Column = point.Offset, + FileName = testFile.FileName + }; + + var getResponse = await getRequestHandler.Handle(getRequest); + Assert.NotNull(getResponse.CodeActions); + Assert.Contains(getResponse.CodeActions, f => f.Name == $"Change namespace to '{expectedNamespace}'"); + } + } + + [Theory] + [InlineData("OmniSharpTest", "Bar.cs")] + [InlineData("OmniSharpTest.Foo", "Foo", "Bar.cs")] + [InlineData("OmniSharpTest.Foo.Bar", "Foo", "Bar", "Baz.cs")] + public async Task RespectFolderName_InExecutedCodeActions(string expectedNamespace, params string[] relativePath) + { + var expected = "namespace " + expectedNamespace + " { }"; + var testFile = new TestFile(Path.Combine(TestAssets.Instance.TestFilesFolder, Path.Combine(relativePath)), @"namespace Xx$$x { }"); + + using (var host = CreateOmniSharpHost(new[] { testFile }, null, TestAssets.Instance.TestFilesFolder)) + { + var point = testFile.Content.GetPointFromPosition(); + var runRequestHandler = host.GetRequestHandler(OmniSharpEndpoints.V2.RunCodeAction); + var runRequest = new RunCodeActionRequest + { + Line = point.Line, + Column = point.Offset, + FileName = testFile.FileName, + Identifier = $"Change namespace to '{expectedNamespace}'", + WantsTextChanges = false, + WantsAllCodeActionOperations = true, + Buffer = testFile.Content.Code + }; + var runResponse = await runRequestHandler.Handle(runRequest); + + AssertIgnoringIndent(expected, ((ModifiedFileResponse)runResponse.Changes.First()).Buffer); + } + } + + [Theory] + [InlineData("LiveChanged", "Bar.cs")] + [InlineData("LiveChanged.Foo", "Foo", "Bar.cs")] + [InlineData("LiveChanged.Foo.Bar", "Foo", "Bar", "Baz.cs")] + public async Task RespectFolderName_InExecutedCodeActions_AfterLiveChange(string expectedNamespace, params string[] relativePath) + { + var expected = "namespace " + expectedNamespace + " { }"; + var testFile = new TestFile(Path.Combine(TestAssets.Instance.TestFilesFolder, Path.Combine(relativePath)), @"namespace Xx$$x { }"); + + using (var host = CreateOmniSharpHost(new[] { testFile }, null, TestAssets.Instance.TestFilesFolder)) + { + var point = testFile.Content.GetPointFromPosition(); + var runRequestHandler = host.GetRequestHandler(OmniSharpEndpoints.V2.RunCodeAction); + + var changedSolution = host.Workspace.CurrentSolution.WithProjectDefaultNamespace(host.Workspace.CurrentSolution.Projects.ElementAt(0).Id, "LiveChanged"); + host.Workspace.TryApplyChanges(changedSolution); + + var runRequest = new RunCodeActionRequest + { + Line = point.Line, + Column = point.Offset, + FileName = testFile.FileName, + Identifier = $"Change namespace to '{expectedNamespace}'", + WantsTextChanges = false, + WantsAllCodeActionOperations = true, + Buffer = testFile.Content.Code + }; + var runResponse = await runRequestHandler.Handle(runRequest); + + AssertIgnoringIndent(expected, ((ModifiedFileResponse)runResponse.Changes.First()).Buffer); + } + } + + [Fact] + public void CheckIfOnDefaultNamespaceChangedIsAvailableInRoslyn() + { + // This tracks the availability of OnDefaultNamespaceChanged on the Workspace + // at the moment it's not and we need to manually sync default namespace after it changes + // when OmniSharp is running by having reflection in + // protected override void ApplyProjectChanges(ProjectChanges projectChanges) + // once it's fixed in Roslyn we can get rid of this test + var onDefaultNamespaceChanged = typeof(OmniSharpWorkspace).GetMethod("OnDefaultNamespaceChanged", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(onDefaultNamespaceChanged); + + var parameters = onDefaultNamespaceChanged.GetParameters(); + Assert.Equal(2, parameters.Count()); + Assert.Equal(typeof(ProjectId), parameters[0].ParameterType); + Assert.Equal(typeof(string), parameters[1].ParameterType); + } + + private static void AssertIgnoringIndent(string expected, string actual) + { + Assert.Equal(TrimLines(expected), TrimLines(actual), false, true, true); + } + + private static string TrimLines(string source) + { + return string.Join("\n", source.Split('\n').Select(s => s.Trim())); + } + } +} diff --git a/tests/TestUtility/AbstractCodeActionsTestFixture.cs b/tests/TestUtility/AbstractCodeActionsTestFixture.cs index 777fa5e140..57cef8f739 100644 --- a/tests/TestUtility/AbstractCodeActionsTestFixture.cs +++ b/tests/TestUtility/AbstractCodeActionsTestFixture.cs @@ -20,7 +20,7 @@ public abstract class AbstractCodeActionsTestFixture protected ITestOutputHelper TestOutput { get; } - private string BufferPath => $"{Path.DirectorySeparatorChar}somepath{Path.DirectorySeparatorChar}buffer{_fileTypeExtension}"; + private string BufferPath => $"{Directory.GetCurrentDirectory()}{Path.DirectorySeparatorChar}somepath{Path.DirectorySeparatorChar}buffer{_fileTypeExtension}"; protected AbstractCodeActionsTestFixture(ITestOutputHelper output, string fileTypeExtension = ".cs") { diff --git a/tests/TestUtility/AbstractTestFixture.cs b/tests/TestUtility/AbstractTestFixture.cs index 3b584599e8..5b48a718e1 100644 --- a/tests/TestUtility/AbstractTestFixture.cs +++ b/tests/TestUtility/AbstractTestFixture.cs @@ -65,7 +65,7 @@ protected OmniSharpTestHost CreateOmniSharpHost(TestFile[] testFiles, IEnumerabl if (testFiles.Length > 0) { - host.AddFilesToWorkspace(testFiles); + host.AddFilesToWorkspace(path, testFiles); } return host; diff --git a/tests/TestUtility/OmniSharpTestHost.cs b/tests/TestUtility/OmniSharpTestHost.cs index e16c5c6a54..476f1bdc17 100644 --- a/tests/TestUtility/OmniSharpTestHost.cs +++ b/tests/TestUtility/OmniSharpTestHost.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Composition.Hosting; using System.Composition.Hosting.Core; +using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; @@ -128,10 +129,14 @@ public THandler GetRequestHandler(string name, string languageName = L } public IEnumerable AddFilesToWorkspace(params TestFile[] testFiles) + => AddFilesToWorkspace(Directory.GetCurrentDirectory(), testFiles); + + public IEnumerable AddFilesToWorkspace(string folderPath, params TestFile[] testFiles) { + folderPath = folderPath ?? Directory.GetCurrentDirectory(); var projects = TestHelpers.AddProjectToWorkspace( - this.Workspace, - "project.csproj", + Workspace, + Path.Combine(folderPath, "project.csproj"), new[] { "net472" }, testFiles.Where(f => f.FileName.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)).ToArray()); diff --git a/tests/TestUtility/TestHelpers.cs b/tests/TestUtility/TestHelpers.cs index 95faa605f1..4217ff5203 100644 --- a/tests/TestUtility/TestHelpers.cs +++ b/tests/TestUtility/TestHelpers.cs @@ -60,20 +60,13 @@ public static IEnumerable AddProjectToWorkspace(OmniSharpWorkspace wo language: LanguageNames.CSharp, filePath: filePath, metadataReferences: references, - analyzerReferences: analyzerRefs); + analyzerReferences: analyzerRefs).WithDefaultNamespace("OmniSharpTest"); workspace.AddProject(projectInfo); foreach (var testFile in testFiles) { - var documentInfo = DocumentInfo.Create( - id: DocumentId.CreateNewId(projectInfo.Id), - name: testFile.FileName, - sourceCodeKind: SourceCodeKind.Regular, - loader: TextLoader.From(TextAndVersion.Create(testFile.Content.Text, versionStamp)), - filePath: testFile.FileName); - - workspace.AddDocument(documentInfo); + workspace.AddDocument(projectInfo.Id, testFile.FileName, TextLoader.From(TextAndVersion.Create(testFile.Content.Text, versionStamp)), SourceCodeKind.Regular); } projectsIds.Add(projectInfo.Id); diff --git a/tests/app.config b/tests/app.config index 445a0e201a..cc24387b07 100644 --- a/tests/app.config +++ b/tests/app.config @@ -7,15 +7,15 @@ - + - + - +