提交 5886ba3d 编写于 作者: C Cyrus Najmabadi

Merge remote-tracking branch 'upstream/master' into depTypeFinderOOP

......@@ -125,6 +125,8 @@
"${workspaceRoot}/scripts/vscode-run-tests.ps1",
"-filePath",
"${file}",
"-msbuildEngine",
"dotnet",
"-framework",
"netcoreapp3.1",
"-filter",
......@@ -142,6 +144,8 @@
"${workspaceRoot}/scripts/vscode-run-tests.ps1",
"-filePath",
"${file}",
"-msbuildEngine",
"dotnet",
"-framework",
"netcoreapp3.1"
],
......
......@@ -60,7 +60,7 @@ This section is broken down by user scenarios, with general solutions listed fir
### Generated class
**User scenario:** As a generator author I want to be able to add a type to the compilation, that can be referenced by the users code.
**User scenario:** As a generator author I want to be able to add a type to the compilation, that can be referenced by the user's code.
**Solution:** Have the user write the code as if the type was already present. Generate the missing type based on information available in the compilation.
......@@ -83,8 +83,11 @@ public partial class UserClass
Create a generator that will create the missing type when run:
```csharp
[Generator]
public class CustomGenerator : ISourceGenerator
{
public void Initialize(InitializationContext context) {}
public void Execute(SourceGeneratorContext context)
{
context.AddSource("myGeneratedFile.cs", SourceText.From($@"
......@@ -112,8 +115,11 @@ namespace GeneratedNamespace
**Example:**
```csharp
[Generator]
public class FileTransformGenerator : ISourceGenerator
{
public void Initialize(InitializationContext context) {}
public void Execute(SourceGeneratorContext context)
{
// find anything that matches our files
......@@ -135,10 +141,12 @@ public class FileTransformGenerator : ISourceGenerator
### Augment user code
**User scenario:** As a generator author I want to be able to augment a users code with new functionality.
**User scenario:** As a generator author I want to be able to inspect and augment a user's code with new functionality.
**Solution:** Require the user to make the class you want to augment be a `partial class`, and mark it with e.g. a unique attribute, or name.
Look for any classes marked for generation and generate a matching `partial class` that contains the additional functionality.
Register a `SyntaxReceiver` that looks for any classes marked for generation and records them. Retrieve the populated `SyntaxReceiver`
during the generation phase and use the recorded information to generate a matching `partial class` that
contains the additional functionality.
**Example:**
......@@ -147,62 +155,63 @@ public partial class UserClass
{
public void UserMethod()
{
// call into a method inside the class
// call into a generated method inside the class
this.GeneratedMethod();
}
}
```
```csharp
[Generator]
public class AugmentingGenerator : ISourceGenerator
{
public void Execute(SourceGeneratorContext context)
public void Initialize(InitializationContext context)
{
var syntaxTrees = context.Compilation.SyntaxTrees;
foreach (var syntaxTree in syntaxTrees)
{
// find the class to augment
var classToAugment = syntaxTree.GetRoot().DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.Where(c => c.Name.ToString() == "UserClass")
.Single();
var sourceText = SourceText.From($@"
public partial class {classToAugment.Identifier.ToString()}
{
private void GeneratedMethod()
{
// generated code
}
}");
context.AddSource("myGeneratedFile.cs", sourceText);
}
// Register a factory that can create our custom syntax receiver
context.RegisterForSyntaxNotifications(() => new MySyntaxReceiver());
}
}
```
### Participate in the IDE experience
public void Execute(SourceGeneratorContext context)
{
// the generator infrastructure will create a receiver and populate it
// we can retrieve the populated instance via the context
MySyntaxReceiver syntaxReceiver = (MySyntaxReceiver)context.SyntaxReceiver;
**User scenario:** As a generator author I want to be able to interactively regenerate code as the user is editing files.
// get the recorded user class
ClassDeclarationSyntax userClass = syntaxReceiver.ClassToAugment;
if (userClass is null)
{
// if we didn't find the user class, there is nothing to do
return;
}
**Solution:** We expect there to be an an opt-in set of interactive interfaces that can be implemented to allow for progressively more complex generation strategies.
It is anticipated there will be a mechanism for providing symbol mapping for lighting up features such a 'Find all references'.
// add the generated implementation to the compilation
SourceText sourceText = SourceText.From($@"
public partial class {userClass.Identifier}
{{
private void GeneratedMethod()
{{
// generated code
}}
}");
context.AddSource("UserClass.Generated.cs", sourceText);
}
```csharp
public class InteractiveGenerator : ISourceGenerator, IAdditionalFilesChangedGenerator
class MySyntaxReceiver : ISyntaxReceiver
{
public void Execute(SourceGeneratorContext context)
{
// generators must always support a total generation pass
}
public ClassDeclarationSyntax ClassToAugment { get; private set; }
public void OnAdditionalFilesChanged(AdditionalFilesChangedContext context)
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
// determine which file changed, and if it affects this generator
// regenerate only the parts that are affected by this change.
// Business logic to decide what we're interested in goes here
if (syntaxNode is ClassDeclarationSyntax cds &&
cds.Identifier.ValueText == "UserClass")
{
ClassToAugment = cds;
}
}
}
}
```
### INotifyPropertyChanged
......@@ -275,6 +284,38 @@ public partial class UserClass : INotifyPropertyChanged
```
### Participate in the IDE experience
**Implementation Status**: Not Implemented.
**User scenario:** As a generator author I want to be able to interactively regenerate code as the user is editing files.
**Solution:** We expect there to be an opt-in set of interactive callbacks that can be implemented to allow for progressively more complex generation strategies.
It is anticipated there will be a mechanism for providing symbol mapping for lighting up features such as 'Find all references'.
```csharp
[Generator]
public class InteractiveGenerator : ISourceGenerator
{
public void Initialize(InitializationContext context)
{
// Register for additional file callbacks
context.RegisterForAdditionalFileChanges(OnAdditionalFilesChanged);
}
public void Execute(SourceGeneratorContext context)
{
// generators must always support a total generation pass
}
public void OnAdditionalFilesChanged(AdditionalFilesChangedContext context)
{
// determine which file changed, and if it affects this generator
// regenerate only the parts that are affected by this change.
}
}
```
### Serialization
**User Scenario**
......@@ -310,7 +351,7 @@ the compiler would notify the generator of every type marked with the given
attribute. For now we'll assume that the types are provided to us.
The first task is to decide what we want our serialization to return. Let's say
we do a simple JSon serialization that produces a string like the following
we do a simple JSON serialization that produces a string like the following
```json
{
......@@ -351,7 +392,7 @@ of code a source generator could add to a compilation.
Our next task is design a generator to generate the above code, since the
above code is itself customized in the `// Body` section according to the
actual properties in the class. In other words, we need to generate the code
which will generate the JSon format. This is a generator-generator.
which will generate the JSON format. This is a generator-generator.
Let's start with a basic template. We are adding a full source generator, so we'll need
to generate a class with the same name as the input class, with a public method called
......@@ -476,8 +517,6 @@ type that opt-ed in to generated serialization. Unlike other technologies,
this serialization mechanism happens entirely at compile time and can be
specialized exactly to what was written in the user class.
### Auto interface implementation
TODO:
......@@ -493,6 +532,6 @@ This section track other miscellaneous TODO items:
**Conventions**: (See TODO in [conventions](#conventions) section above). What standard conventions are we suggesting to users?
**Partial methods**: Should we provide a scenario that includes partial methods? Reasons:
- Control of name. The developer can control the name of the member
- Generation is optional/depending on other state. Based on other information, generator might decide that the method isn't needed.
- Control of name. The developer can control the name of the member
- Generation is optional/depending on other state. Based on other information, generator might decide that the method isn't needed.
......@@ -28,107 +28,121 @@ namespace Microsoft.CodeAnalysis
{
public interface ISourceGenerator
{
void Initialize(InitializationContext context);
void Execute(SourceGeneratorContext context);
}
}
```
Generator implementations are defined in external assemblies passed to the compiler
using the same `-analyzer:` option used for diagnostic analyzers.
using the same `-analyzer:` option used for diagnostic analyzers. Implementations are required to
be annotated with a `Microsoft.CodeAnalysis.GeneratorAttribute` attribute.
An assembly can contain a mix of diagnostic analyzers and source generators.
Since generators are loaded from external assemblies, a generator cannot be used to build
the assembly in which it is defined.
`ISourceGenerator` has a single `Execute` method that is called by the host (either the IDE
or the command-line compiler). `Execute` passes an instance of `SourceGeneratorContext` that provides access
to the `Compilation` and allows the generator to alter it by adding source and reporting diagnostics.
`ISourceGenerator` has an `Initialize` method that is called by the host (either the IDE or
the command-line compiler) exactly once. `Initialize` passes an instance of `InitializationContext`
which can be used by the generator to register a set of callbacks that affect how future generation
passes will occur.
The main generation pass occurs via the `Execute` method. `Execute` passes an instance of `SourceGeneratorContext`
that provides access to the current `Compilation` and allows the generator to alter the resulting output `Compilation`
by adding source and reporting diagnostics.
The generator is also able to access any `AnalyzerAdditionalFiles` passed to the compiler via the `AdditionalFiles`
collection, allowing for generation decisions to based on more than just the user's C# code.
```csharp
namespace Microsoft.CodeAnalysis
{
public struct SourceGeneratorContext
public readonly struct SourceGeneratorContext
{
public Compilation Compilation { get; }
// TODO: replace AnalyzerOptions with an differently named type that is otherwise identical.
// The concern being that something added to one isn't necessarily applicable to the other.
public AnalyzerOptions AnalyzerOptions { get; }
public ImmutableArray<AdditionalText> AdditionalFiles { get; }
public CancellationToken CancellationToken { get; }
// TODO: we need to add a way of declaring diagnostic descriptors, presumably the same mechanism used by analyzers
public Compilation Compilation { get; }
public ISyntaxReceiver? SyntaxReceiver { get; }
public void ReportDiagnostic(Diagnostic diagnostic) { throw new NotImplementedException(); }
public void AddSource(string fileNameHint, SourceText sourceText) { throw new NotImplementedException(); }
}
}
```
It is assumed that some generators will want to generate more than one `SourceText`, for example in a 1:1 mapping
It is assumed that some generators will want to generate more than one `SourceText`, for example in a 1:1 mapping
for additional files. The `fileNameHint` parameter of `AddSource` is intended to address this:
1. If the generated files are emitted to disk, having some ability to put some distinguishing text might be useful.
1. If the generated files are emitted to disk, having some ability to put some distinguishing text might be useful.
For example, if you have two `.resx` files, generating the files with simply names of `ResxGeneratedFile1.cs` and
`ResxGeneratedFile2.cs` wouldn't be terribly useful -- you'd want it to be something like
`ResxGeneratedFile-Strings.cs` and `ResxGeneratedFile-Icons.cs` if you had two `.resx` files
named "Strings" and "Icons" respectively.
2. The IDE needs some concept of a "stable" identifier. Source generators create a couple of fun problems for the IDE:
users will want to be able to set breakpoints in a generated file, for example. If a source generator outputs multiple
files we need to know which is which so we can know which file the breakpoints go with. A source generator of course is
allowed to stop emitting a file if its inputs change (if you delete a `.resx`, then the generated file associated with it
2. The IDE needs some concept of a "stable" identifier. Source generators create a couple of fun problems for the IDE:
users will want to be able to set breakpoints in a generated file, for example. If a source generator outputs multiple
files we need to know which is which so we can know which file the breakpoints go with. A source generator of course is
allowed to stop emitting a file if its inputs change (if you delete a `.resx`, then the generated file associated with it
will also go away), but this gives us some control here.
This was called "hint" in that the compiler is implicitly allowed to control the filename in however it ultimately
needs, and if two source generators give the same "hint" it can still distinguish them with any sort of
This was called "hint" in that the compiler is implicitly allowed to control the filename in however it ultimately
needs, and if two source generators give the same "hint" it can still distinguish them with any sort of
prefix/suffix as necessary.
### IDE Integration
One of the more complicated aspects of supporting generators is enabling a high-fidelity
experience in Visual Studio. For the purposes of determining code correctness, it is
expected that all generators will have had to be run. Obviously, it is impractical to run
every generator on every keystroke, and still maintain an acceptable level of performance
One of the more complicated aspects of supporting generators is enabling a high-fidelity
experience in Visual Studio. For the purposes of determining code correctness, it is
expected that all generators will have had to be run. Obviously, it is impractical to run
every generator on every keystroke, and still maintain an acceptable level of performance
within the IDE.
#### Progressive complexity opt-in
It is expected instead that source generators would work on an 'opt-in' approach to IDE
enablement.
It is expected instead that source generators would work on an 'opt-in' approach to IDE
enablement.
By default, a generator implementing only `ISourceGenerator` would see no IDE integration
and only be correct at build time. Based on conversations with 1st party customers,
and only be correct at build time. Based on conversations with 1st party customers,
there are several cases where this would be enough.
However, for scenarios such as code first gRPC, and in particular Razor and Blazor,
the IDE will need to be able to generate code on-they-fly as those file types are
edited and reflect the changes back to other files in the IDE in near real-time.
However, for scenarios such as code first gRPC, and in particular Razor and Blazor,
the IDE will need to be able to generate code on-they-fly as those file types are
edited and reflect the changes back to other files in the IDE in near real-time.
The proposal is to have a set of advanced interfaces that can be optionally implemented,
The proposal is to have a set of advanced callbacks that can be optionally implemented,
that would allow the IDE to query the generator to decide what needs to be run in the case
of any particular edit.
For example an extension that would cause generation to run after saving a third party
file might look something like:
file might look something like:
```csharp
namespace Microsoft.CodeAnalysis
{
public interface ITriggeredByAdditionalFileSavedGenerator : ISourceGenerator
public struct InitializationContext
{
ImmutableArray<string> SupportedAdditionalFileExtensions { get; }
public void RegisterForAdditionalFileChanges(EditCallback<AdditionalFileEdit> callback){ }
}
}
```
This would allow the generator to register a callback during initialization that would be invoked
every time an additional file changes.
It is expected that there will be various levels of opt in, that can be added to a generator
in order to achieve the specific level of performance required of it.
in order to achieve the specific level of performance required of it.
What these exact APIs will look like remains an open question, and it's expected that we will
What these exact APIs will look like remains an open question, and it's expected that we will
need to prototype some real-world generators before knowing what their precise shape will be.
### Output files
It it desirable that the generated source texts be available for inspection after generation,
It is desirable that the generated source texts be available for inspection after generation,
either as part of creating a generator or seeing what code was generated by a third party
generator.
......
param (
[Parameter(Mandatory = $true)][string]$filePath,
[string]$msbuildEngine = "vs"
[string]$msbuildEngine = "vs",
[string]$framework = ""
)
Set-StrictMode -version 3.0
......@@ -12,7 +13,8 @@ $fileInfo = Get-ItemProperty $filePath
$projectFileInfo = Get-ProjectFile $fileInfo
if ($projectFileInfo) {
$buildTool = InitializeBuildTool
$buildArgs = "$($buildTool.Command) -v:m -m -p:UseRoslynAnalyzers=false -p:GenerateFullPaths=true $($projectFileInfo.FullName)"
$frameworkArg = if ($framework -ne "") { " -p:TargetFramework=$framework" } else { "" }
$buildArgs = "$($buildTool.Command) -v:m -m -p:UseRoslynAnalyzers=false -p:GenerateFullPaths=true$frameworkArg $($projectFileInfo.FullName)"
Write-Host "$($buildTool.Path) $buildArgs"
Exec-Console $buildTool.Path $buildArgs
......
param (
[Parameter(Mandatory = $true)][string]$filePath,
[Parameter(Mandatory = $false)][string]$framework,
[Parameter(Mandatory = $false)][string]$filter
[string]$msbuildEngine = "vs",
[string]$framework = $null,
[string]$filter = ""
)
Set-StrictMode -version 3.0
......@@ -9,6 +10,10 @@ $ErrorActionPreference = "Stop"
. (Join-Path $PSScriptRoot "../eng/build-utils.ps1")
# Run a build
. (Join-Path $PSScriptRoot "./vscode-build.ps1") -filePath $filePath -framework $framework -msbuildEngine $msbuildEngine
Write-Output ""
$fileInfo = Get-ItemProperty $filePath
$projectFileInfo = Get-ProjectFile $fileInfo
if ($projectFileInfo) {
......@@ -23,9 +28,9 @@ if ($projectFileInfo) {
$resultsPath = Resolve-Path (New-Item -ItemType Directory -Force -Path $resultsPath) -Relative
# Remove old run logs with the same prefix
Remove-Item (Join-Path $resultsPath "$logFilePrefix*.html")
Remove-Item (Join-Path $resultsPath "$logFilePrefix*.html") -ErrorAction SilentlyContinue
$invocation = "$dotnetPath test $projectDir" + $filterArg + $frameworkArg + " --logger `"html;LogFilePrefix=$logfilePrefix`" --results-directory $resultsPath"
$invocation = "$dotnetPath test $projectDir" + $filterArg + $frameworkArg + " --logger `"html;LogFilePrefix=$logfilePrefix`" --results-directory $resultsPath --no-build"
Write-Output "> $invocation"
Invoke-Expression $invocation
......
......@@ -64,7 +64,10 @@ internal static class IStreamingFindUsagesPresenterExtensions
var externalItems = definitions.WhereAsArray(d => d.IsExternal);
foreach (var item in externalItems)
{
if (item.TryNavigateTo(workspace, isPreview: true))
// If we're directly going to a location we need to activate the preview so
// that focus follows to the new cursor position. This behavior is expected
// because we are only going to navigate once successfully
if (item.TryNavigateTo(workspace, NavigationBehavior.PreviewWithFocus))
{
return true;
}
......@@ -80,7 +83,9 @@ internal static class IStreamingFindUsagesPresenterExtensions
nonExternalItems[0].SourceSpans.Length <= 1)
{
// There was only one location to navigate to. Just directly go to that location.
return nonExternalItems[0].TryNavigateTo(workspace, isPreview: true);
// If we're directly going to a location we need to activate the preview so
// that focus follows to the new cursor position.
return nonExternalItems[0].TryNavigateTo(workspace, NavigationBehavior.PreviewWithFocus);
}
if (presenter != null)
......
......@@ -7,10 +7,12 @@
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.ConvertTupleToStruct;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.CSharp.ConvertTupleToStruct
{
[ExtensionOrder(Before = PredefinedCodeRefactoringProviderNames.IntroduceVariable)]
[ExportLanguageService(typeof(IConvertTupleToStructCodeRefactoringProvider), LanguageNames.CSharp)]
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(PredefinedCodeRefactoringProviderNames.ConvertTupleToStruct)), Shared]
internal class CSharpConvertTupleToStructCodeRefactoringProvider :
AbstractConvertTupleToStructCodeRefactoringProvider<
......@@ -30,12 +32,5 @@ internal class CSharpConvertTupleToStructCodeRefactoringProvider :
public CSharpConvertTupleToStructCodeRefactoringProvider()
{
}
protected override ObjectCreationExpressionSyntax CreateObjectCreationExpression(
NameSyntax nameNode, SyntaxToken openParen, SeparatedSyntaxList<ArgumentSyntax> arguments, SyntaxToken closeParen)
{
return SyntaxFactory.ObjectCreationExpression(
nameNode, SyntaxFactory.ArgumentList(openParen, arguments, closeParen), initializer: null);
}
}
}
......@@ -9,6 +9,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CodeRefactorings;
......@@ -16,8 +17,11 @@
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.GenerateEqualsAndGetHashCodeFromMembers;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Simplification;
......@@ -37,7 +41,7 @@ internal abstract partial class AbstractConvertTupleToStructCodeRefactoringProvi
TTupleTypeSyntax,
TTypeBlockSyntax,
TNamespaceDeclarationSyntax>
: CodeRefactoringProvider
: CodeRefactoringProvider, IConvertTupleToStructCodeRefactoringProvider
where TExpressionSyntax : SyntaxNode
where TNameSyntax : TExpressionSyntax
where TIdentifierNameSyntax : TNameSyntax
......@@ -49,17 +53,6 @@ internal abstract partial class AbstractConvertTupleToStructCodeRefactoringProvi
where TTypeBlockSyntax : SyntaxNode
where TNamespaceDeclarationSyntax : SyntaxNode
{
private enum Scope
{
ContainingMember,
ContainingType,
ContainingProject,
DependentProjects
}
protected abstract TObjectCreationExpressionSyntax CreateObjectCreationExpression(
TNameSyntax nameNode, SyntaxToken openParen, SeparatedSyntaxList<TArgumentSyntax> arguments, SyntaxToken closeParen);
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var (document, textSpan, cancellationToken) = context;
......@@ -75,15 +68,15 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
// If it does, we can't convert this. There is no way to describe this anonymous type
// in the concrete type we create.
var fields = tupleType.TupleElements;
var containsAnonymousType = fields.Any(p => p.Type.ContainsAnonymousType());
var containsAnonymousType = fields.Any<IFieldSymbol>(p => p.Type.ContainsAnonymousType());
if (containsAnonymousType)
{
return;
}
var capturedTypeParameters =
fields.Select(p => p.Type)
.SelectMany(t => t.GetReferencedTypeParameters())
fields.Select<IFieldSymbol, ITypeSymbol>(p => p.Type)
.SelectMany<ITypeSymbol, ITypeParameterSymbol>(t => t.GetReferencedTypeParameters())
.Distinct()
.ToImmutableArray();
......@@ -112,7 +105,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
//
// this means we can only find tuples like ```(x: 1, ...)``` but not ```(1, 2)```. The
// latter has members called Item1 and Item2, but those names don't show up in source.
if (fields.All(f => f.CorrespondingTupleField != f))
if (fields.All<IFieldSymbol>(f => f.CorrespondingTupleField != f))
{
scopes.Add(CreateAction(context, Scope.ContainingProject));
scopes.Add(CreateAction(context, Scope.DependentProjects));
......@@ -141,7 +134,7 @@ private static string GetTitle(Scope scope)
_ => throw ExceptionUtilities.UnexpectedValue(scope),
};
private async Task<(SyntaxNode, INamedTypeSymbol)> TryGetTupleInfoAsync(
private static async Task<(SyntaxNode, INamedTypeSymbol)> TryGetTupleInfoAsync(
Document document, TextSpan span, CancellationToken cancellationToken)
{
// Enable refactoring either for TupleExpression or TupleType
......@@ -163,7 +156,59 @@ private static string GetTitle(Scope scope)
return (expressionOrType, tupleType);
}
private async Task<Solution> ConvertToStructAsync(
public async Task<Solution> ConvertToStructAsync(
Document document, TextSpan span, Scope scope, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (Logger.LogBlock(FunctionId.AbstractConvertTupleToStructCodeRefactoringProvider_ConvertToStructAsync, cancellationToken))
{
var solution = document.Project.Solution;
var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var resultOpt = await client.TryRunRemoteAsync<SerializableConvertTupleToStructResult>(
WellKnownServiceHubServices.CodeAnalysisService,
nameof(IRemoteConvertTupleToStructCodeRefactoringProvider.ConvertToStructAsync),
solution,
new object[]
{
document.Id,
span,
scope,
},
callbackTarget: null,
cancellationToken).ConfigureAwait(false);
if (resultOpt.HasValue)
{
var result = resultOpt.Value;
var resultSolution = await RemoteUtilities.UpdateSolutionAsync(
solution, result.DocumentTextChanges, cancellationToken).ConfigureAwait(false);
return await AddRenameTokenAsync(
resultSolution, result.RenamedToken, cancellationToken).ConfigureAwait(false);
}
}
}
return await ConvertToStructInCurrentProcessAsync(
document, span, scope, cancellationToken).ConfigureAwait(false);
}
private async Task<Solution> AddRenameTokenAsync(
Solution solution,
(DocumentId documentId, TextSpan span) renamedToken,
CancellationToken cancellationToken)
{
var document = solution.GetDocument(renamedToken.documentId);
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var token = root.FindToken(renamedToken.span.Start);
var newRoot = root.ReplaceToken(token, token.WithAdditionalAnnotations(RenameAnnotation.Create()));
return document.WithSyntaxRoot(newRoot).Project.Solution;
}
private static async Task<Solution> ConvertToStructInCurrentProcessAsync(
Document document, TextSpan span, Scope scope, CancellationToken cancellationToken)
{
var (tupleExprOrTypeNode, tupleType) = await TryGetTupleInfoAsync(
......@@ -187,8 +232,8 @@ private static string GetTitle(Scope scope)
"NewStruct", n => semanticModel.LookupSymbols(position, name: n).IsEmpty);
var capturedTypeParameters =
tupleType.TupleElements.Select(p => p.Type)
.SelectMany(t => t.GetReferencedTypeParameters())
tupleType.TupleElements.Select<IFieldSymbol, ITypeSymbol>(p => p.Type)
.SelectMany<ITypeSymbol, ITypeParameterSymbol>(t => t.GetReferencedTypeParameters())
.Distinct()
.ToImmutableArray();
......@@ -219,7 +264,7 @@ private static string GetTitle(Scope scope)
return updatedSolution;
}
private async Task ReplaceExpressionAndTypesInScopeAsync(
private static async Task ReplaceExpressionAndTypesInScopeAsync(
Dictionary<Document, SyntaxEditor> documentToEditorMap,
ImmutableArray<DocumentToUpdate> documentsToUpdate,
SyntaxNode tupleExprOrTypeNode, INamedTypeSymbol tupleType,
......@@ -355,12 +400,12 @@ private static string GetTitle(Scope scope)
// starting project.
var dependentProjects = graph.GetProjectsThatDirectlyDependOnThisProject(startingProject.Id);
var allProjects = dependentProjects.Select(solution.GetProject)
var allProjects = dependentProjects.Select<ProjectId, Project>(solution.GetProject)
.Where(p => p.SupportsCompilation)
.Concat(startingProject).ToSet();
var result = ArrayBuilder<DocumentToUpdate>.GetInstance();
var tupleFieldNames = tupleType.TupleElements.SelectAsArray(f => f.Name);
var tupleFieldNames = tupleType.TupleElements.SelectAsArray<IFieldSymbol, string>(f => f.Name);
foreach (var project in allProjects)
{
......@@ -375,7 +420,7 @@ private static string GetTitle(Scope scope)
Project project, INamedTypeSymbol tupleType, CancellationToken cancellationToken)
{
var result = ArrayBuilder<DocumentToUpdate>.GetInstance();
var tupleFieldNames = tupleType.TupleElements.SelectAsArray(f => f.Name);
var tupleFieldNames = tupleType.TupleElements.SelectAsArray<IFieldSymbol, string>(f => f.Name);
await AddDocumentsToUpdateForProjectAsync(
project, result, tupleFieldNames, cancellationToken).ConfigureAwait(false);
......@@ -427,7 +472,7 @@ private static bool InfoProbablyContainsTupleFieldNames(SyntaxTreeIndex info, Im
foreach (var group in declarationService.GetDeclarations(typeSymbol).GroupBy(r => r.SyntaxTree))
{
var document = solution.GetDocument(group.Key);
var nodes = group.SelectAsArray(r => r.GetSyntax(cancellationToken));
var nodes = group.SelectAsArray<SyntaxReference, SyntaxNode>(r => r.GetSyntax(cancellationToken));
result.Add(new DocumentToUpdate(document, nodes));
}
......@@ -506,7 +551,7 @@ private static bool InfoProbablyContainsTupleFieldNames(SyntaxTreeIndex info, Im
return currentSolution;
}
private async Task<bool> ReplaceTupleExpressionsAndTypesInDocumentAsync(
private static async Task<bool> ReplaceTupleExpressionsAndTypesInDocumentAsync(
Document document, SyntaxEditor editor, SyntaxNode startingNode,
INamedTypeSymbol tupleType, TNameSyntax fullyQualifiedStructName,
string structName, ImmutableArray<ITypeParameterSymbol> typeParameters,
......@@ -526,7 +571,7 @@ private static bool InfoProbablyContainsTupleFieldNames(SyntaxTreeIndex info, Im
return changed;
}
private async Task<bool> ReplaceMatchingTupleExpressionsAsync(
private static async Task<bool> ReplaceMatchingTupleExpressionsAsync(
Document document, SyntaxEditor editor, SyntaxNode startingNode,
INamedTypeSymbol tupleType, TNameSyntax qualifiedTypeName,
string typeName, ImmutableArray<ITypeParameterSymbol> typeParameters,
......@@ -552,8 +597,7 @@ private static bool InfoProbablyContainsTupleFieldNames(SyntaxTreeIndex info, Im
{
changed = true;
ReplaceWithObjectCreation(
syntaxFacts, editor, typeName, typeParameters,
qualifiedTypeName, startingNode, childCreation);
editor, typeName, typeParameters, qualifiedTypeName, startingNode, childCreation);
}
}
......@@ -583,8 +627,8 @@ private static bool AreEquivalent(StringComparer comparer, INamedTypeSymbol tupl
return true;
}
private void ReplaceWithObjectCreation(
ISyntaxFactsService syntaxFacts, SyntaxEditor editor, string typeName, ImmutableArray<ITypeParameterSymbol> typeParameters,
private static void ReplaceWithObjectCreation(
SyntaxEditor editor, string typeName, ImmutableArray<ITypeParameterSymbol> typeParameters,
TNameSyntax qualifiedTypeName, SyntaxNode startingCreationNode, TTupleExpressionSyntax childCreation)
{
// Use the callback form as tuples types may be nested, and we want to
......@@ -600,33 +644,34 @@ private static bool AreEquivalent(StringComparer comparer, INamedTypeSymbol tupl
? CreateStructNameNode(g, typeName, typeParameters, addRenameAnnotation: true)
: qualifiedTypeName;
var syntaxFacts = g.SyntaxFacts;
syntaxFacts.GetPartsOfTupleExpression<TArgumentSyntax>(
currentTupleExpr, out var openParen, out var arguments, out var closeParen);
arguments = ConvertArguments(syntaxFacts, g, arguments);
arguments = ConvertArguments(g, arguments);
return CreateObjectCreationExpression(typeNameNode, openParen, arguments, closeParen)
return g.ObjectCreationExpression(typeNameNode, openParen, arguments, closeParen)
.WithAdditionalAnnotations(Formatter.Annotation);
});
}
private SeparatedSyntaxList<TArgumentSyntax> ConvertArguments(ISyntaxFactsService syntaxFacts, SyntaxGenerator generator, SeparatedSyntaxList<TArgumentSyntax> arguments)
=> generator.SeparatedList<TArgumentSyntax>(ConvertArguments(syntaxFacts, generator, arguments.GetWithSeparators()));
private static SeparatedSyntaxList<TArgumentSyntax> ConvertArguments(SyntaxGenerator generator, SeparatedSyntaxList<TArgumentSyntax> arguments)
=> generator.SeparatedList<TArgumentSyntax>(ConvertArguments(generator, arguments.GetWithSeparators()));
private SyntaxNodeOrTokenList ConvertArguments(ISyntaxFactsService syntaxFacts, SyntaxGenerator generator, SyntaxNodeOrTokenList list)
=> new SyntaxNodeOrTokenList(list.Select(v => ConvertArgumentOrToken(syntaxFacts, generator, v)));
private static SyntaxNodeOrTokenList ConvertArguments(SyntaxGenerator generator, SyntaxNodeOrTokenList list)
=> new SyntaxNodeOrTokenList(list.Select(v => ConvertArgumentOrToken(generator, v)));
private SyntaxNodeOrToken ConvertArgumentOrToken(ISyntaxFactsService syntaxFacts, SyntaxGenerator generator, SyntaxNodeOrToken arg)
private static SyntaxNodeOrToken ConvertArgumentOrToken(SyntaxGenerator generator, SyntaxNodeOrToken arg)
=> arg.IsToken
? arg
: ConvertArgument(syntaxFacts, generator, (TArgumentSyntax)arg.AsNode());
: ConvertArgument(generator, (TArgumentSyntax)arg.AsNode());
private TArgumentSyntax ConvertArgument(
ISyntaxFactsService syntaxFacts, SyntaxGenerator generator, TArgumentSyntax argument)
private static TArgumentSyntax ConvertArgument(
SyntaxGenerator generator, TArgumentSyntax argument)
{
// Keep named arguments for literal args. It helps keep the code self-documenting.
// Remove for complex args as it's most likely just clutter a person doesn't need
// when instantiating their new type.
var expr = syntaxFacts.GetExpressionOfArgument(argument);
var expr = generator.SyntaxFacts.GetExpressionOfArgument(argument);
if (expr is TLiteralExpressionSyntax)
{
return argument;
......@@ -635,7 +680,7 @@ private SyntaxNodeOrToken ConvertArgumentOrToken(ISyntaxFactsService syntaxFacts
return (TArgumentSyntax)generator.Argument(expr).WithTriviaFrom(argument);
}
private async Task<bool> ReplaceMatchingTupleTypesAsync(
private static async Task<bool> ReplaceMatchingTupleTypesAsync(
Document document, SyntaxEditor editor, SyntaxNode startingNode,
INamedTypeSymbol tupleType, TNameSyntax qualifiedTypeName,
string typeName, ImmutableArray<ITypeParameterSymbol> typeParameters,
......@@ -667,7 +712,7 @@ private SyntaxNodeOrToken ConvertArgumentOrToken(ISyntaxFactsService syntaxFacts
return changed;
}
private void ReplaceWithTypeNode(
private static void ReplaceWithTypeNode(
SyntaxEditor editor, string typeName, ImmutableArray<ITypeParameterSymbol> typeParameters,
TNameSyntax qualifiedTypeName, SyntaxNode startingNode, TTupleTypeSyntax childTupleType)
{
......@@ -751,7 +796,7 @@ private SyntaxNodeOrToken ConvertArgumentOrToken(ISyntaxFactsService syntaxFacts
explicitInterfaceImplementations: default,
WellKnownMemberNames.DeconstructMethodName,
typeParameters: default,
constructor.Parameters.SelectAsArray(p =>
constructor.Parameters.SelectAsArray<IParameterSymbol, IParameterSymbol>(p =>
CodeGenerationSymbolFactory.CreateParameterSymbol(RefKind.Out, p.Type, p.Name)),
assignments);
}
......@@ -763,7 +808,7 @@ private SyntaxNodeOrToken ConvertArgumentOrToken(ISyntaxFactsService syntaxFacts
const string valueName = "value";
var valueNode = generator.IdentifierName(valueName);
var arguments = tupleType.TupleElements.SelectAsArray(
var arguments = tupleType.TupleElements.SelectAsArray<IFieldSymbol, SyntaxNode>(
field => generator.Argument(
generator.MemberAccessExpression(valueNode, field.Name)));
......@@ -810,7 +855,7 @@ private SyntaxNodeOrToken ConvertArgumentOrToken(ISyntaxFactsService syntaxFacts
// For every property, create a corresponding parameter, as well as an assignment
// statement from that parameter to the property.
var parameterToPropMap = new Dictionary<string, ISymbol>();
var parameters = fields.SelectAsArray(field =>
var parameters = fields.SelectAsArray<IFieldSymbol, IParameterSymbol>(field =>
{
var parameter = CodeGenerationSymbolFactory.CreateParameterSymbol(
field.Type, field.Name.ToCamelCase(trimLeadingTypePrefix: false));
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.ConvertTupleToStruct
{
internal interface IConvertTupleToStructCodeRefactoringProvider : ILanguageService
{
Task<Solution> ConvertToStructAsync(
Document document, TextSpan span, Scope scope, CancellationToken cancellationToken);
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.ConvertTupleToStruct
{
internal interface IRemoteConvertTupleToStructCodeRefactoringProvider
{
Task<SerializableConvertTupleToStructResult> ConvertToStructAsync(
PinnedSolutionInfo solutionInfo,
DocumentId documentId,
TextSpan span,
Scope scope,
CancellationToken cancellationToken);
}
internal class SerializableConvertTupleToStructResult
{
public (DocumentId, TextChange[])[] DocumentTextChanges;
public (DocumentId, TextSpan) RenamedToken;
}
}
// 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.ConvertTupleToStruct
{
internal enum Scope
{
ContainingMember,
ContainingType,
ContainingProject,
DependentProjects
}
}
......@@ -17,13 +17,16 @@ public static bool CanNavigateTo(this DocumentSpan documentSpan)
return service.CanNavigateToSpan(workspace, documentSpan.Document.Id, documentSpan.SourceSpan);
}
public static bool TryNavigateTo(this DocumentSpan documentSpan, bool isPreview)
public static bool TryNavigateTo(this DocumentSpan documentSpan, NavigationBehavior navigationBehavior)
{
var solution = documentSpan.Document.Project.Solution;
var workspace = solution.Workspace;
var service = workspace.Services.GetService<IDocumentNavigationService>();
return service.TryNavigateToSpan(workspace, documentSpan.Document.Id, documentSpan.SourceSpan,
options: solution.Options.WithChangedOption(NavigationOptions.PreferProvisionalTab, isPreview));
var options = solution.Options.WithChangedOption(NavigationOptions.PreferProvisionalTab, navigationBehavior != NavigationBehavior.Normal);
options = options.WithChangedOption(NavigationOptions.ActivateProvisionalTab, navigationBehavior == NavigationBehavior.PreviewWithFocus);
return service.TryNavigateToSpan(workspace, documentSpan.Document.Id, documentSpan.SourceSpan, options);
}
public static async Task<bool> IsHiddenAsync(
......
......@@ -50,7 +50,7 @@ public override bool CanNavigateTo(Workspace workspace)
return SourceSpans[0].CanNavigateTo();
}
public override bool TryNavigateTo(Workspace workspace, bool isPreview)
public override bool TryNavigateTo(Workspace workspace, NavigationBehavior navigationBehavior)
{
if (Properties.ContainsKey(NonNavigable))
{
......@@ -62,7 +62,7 @@ public override bool TryNavigateTo(Workspace workspace, bool isPreview)
return TryNavigateToMetadataSymbol(workspace, symbolKey);
}
return SourceSpans[0].TryNavigateTo(isPreview);
return SourceSpans[0].TryNavigateTo(navigationBehavior);
}
private bool CanNavigateToMetadataSymbol(Workspace workspace, string symbolKey)
......
......@@ -152,9 +152,8 @@ internal abstract partial class DefinitionItem
Contract.ThrowIfFalse(Properties.ContainsKey(MetadataSymbolOriginatingProjectIdDebugName));
}
}
public abstract bool CanNavigateTo(Workspace workspace);
public abstract bool TryNavigateTo(Workspace workspace, bool isPreview);
public abstract bool TryNavigateTo(Workspace workspace, NavigationBehavior navigationBehavior);
public static DefinitionItem Create(
ImmutableArray<string> tags,
......
......@@ -13,5 +13,12 @@ internal static class NavigationOptions
/// be used for any document that needs to be opened, if one is available.
/// </summary>
public static readonly Option2<bool> PreferProvisionalTab = new Option2<bool>(nameof(NavigationOptions), nameof(PreferProvisionalTab), defaultValue: false);
/// <summary>
/// This option can be passed to the <see cref="IDocumentNavigationService"/> APIs to request that if a provisional tab
/// <see cref="PreferProvisionalTab"/> is used the navigation should still activate the tab. Defaults to false to support
/// users not losing focus while navigating through lists such as find references.
/// </summary>
public static readonly Option2<bool> ActivateProvisionalTab = new Option2<bool>(nameof(NavigationOptions), nameof(ActivateProvisionalTab), defaultValue: false);
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
namespace Microsoft.CodeAnalysis
{
internal enum NavigationBehavior
{
/// <summary>
/// The destination will attempt to open in a normal tab and activate
/// </summary>
Normal,
/// <summary>
/// The destination will navigate using a preview window and not activate that window.
/// Useful for cases where the user might be going through a list of items and we want to
/// make the context visible but not make focus changes
/// </summary>
PreviewWithoutFocus,
/// <summary>
/// The destination will navigate using a preview window and activate
/// </summary>
PreviewWithFocus
}
}
......@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.DocumentHighlighting;
......@@ -60,10 +59,6 @@ internal static class ProtocolConversions
{ WellKnownTags.NuGet, LSP.CompletionItemKind.Text }
};
/// <summary>
/// Workaround for razor file paths being provided with a preceding slash on windows.
/// Long term fix in razor here - https://github.com/dotnet/aspnetcore/issues/19948
/// </summary>
public static Uri GetUriFromFilePath(string filePath)
{
if (filePath is null)
......@@ -71,12 +66,6 @@ public static Uri GetUriFromFilePath(string filePath)
throw new ArgumentNullException(nameof(filePath));
}
// Remove preceding slash if we're on Window as it's an invalid URI.
if (filePath.StartsWith("/") && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
filePath = filePath.Substring(1);
}
return new Uri(filePath, UriKind.Absolute);
}
......
......@@ -6,10 +6,12 @@ Imports System.Composition
Imports System.Diagnostics.CodeAnalysis
Imports Microsoft.CodeAnalysis.CodeRefactorings
Imports Microsoft.CodeAnalysis.ConvertTupleToStruct
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.ConvertTupleToStruct
<ExtensionOrder(Before:=PredefinedCodeRefactoringProviderNames.IntroduceVariable)>
<ExportLanguageService(GetType(IConvertTupleToStructCodeRefactoringProvider), LanguageNames.VisualBasic)>
<ExportCodeRefactoringProvider(LanguageNames.VisualBasic, Name:=PredefinedCodeRefactoringProviderNames.ConvertTupleToStruct), [Shared]>
Friend Class VisualBasicConvertTupleToStructCodeRefactoringProvider
Inherits AbstractConvertTupleToStructCodeRefactoringProvider(Of
......@@ -28,12 +30,5 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ConvertTupleToStruct
<SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification:="Used in test code: https://github.com/dotnet/roslyn/issues/42814")>
Public Sub New()
End Sub
Protected Overrides Function CreateObjectCreationExpression(
nameNode As NameSyntax, openParen As SyntaxToken, arguments As SeparatedSyntaxList(Of ArgumentSyntax), closeParen As SyntaxToken) As ObjectCreationExpressionSyntax
Return SyntaxFactory.ObjectCreationExpression(
attributeLists:=Nothing, nameNode, SyntaxFactory.ArgumentList(openParen, arguments, closeParen), initializer:=Nothing)
End Function
End Class
End Namespace
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Experiments;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Remote;
namespace Microsoft.CodeAnalysis.ExternalAccess.Razor
{
internal sealed class RazorRemoteHostClient
{
private readonly RemoteHostClient _client;
private readonly string _serviceName;
internal RazorRemoteHostClient(RemoteHostClient client, string serviceName)
{
_client = client;
_serviceName = serviceName;
}
public static async Task<RazorRemoteHostClient?> CreateAsync(Workspace workspace, CancellationToken cancellationToken = default)
{
var clientFactory = workspace.Services.GetRequiredService<IRemoteHostClientService>();
var client = await clientFactory.TryGetRemoteHostClientAsync(cancellationToken).ConfigureAwait(false);
return client == null ? null : new RazorRemoteHostClient(client, GetServiceName(workspace));
}
public Task<Optional<T>> TryRunRemoteAsync<T>(string targetName, Solution? solution, IReadOnlyList<object?> arguments, CancellationToken cancellationToken)
=> _client.TryRunRemoteAsync<T>(_serviceName, targetName, solution, arguments, callbackTarget: null, cancellationToken);
#region support a/b testing. after a/b testing, we can remove all this code
private static string? s_lazyServiceName = null;
private static string GetServiceName(Workspace workspace)
{
if (s_lazyServiceName == null)
{
var x64 = workspace.Options.GetOption(OOP64Bit);
if (!x64)
{
x64 = workspace.Services.GetRequiredService<IExperimentationService>().IsExperimentEnabled(
WellKnownExperimentNames.RoslynOOP64bit);
}
Interlocked.CompareExchange(
ref s_lazyServiceName, x64 ? "razorLanguageService64" : "razorLanguageService", null);
}
return s_lazyServiceName;
}
public static readonly Option<bool> OOP64Bit = new Option<bool>(
nameof(InternalFeatureOnOffOptions), nameof(OOP64Bit), defaultValue: false,
storageLocations: new LocalUserProfileStorageLocation(InternalFeatureOnOffOptions.LocalRegistryPath + nameof(OOP64Bit)));
private static class InternalFeatureOnOffOptions
{
internal const string LocalRegistryPath = @"Roslyn\Internal\OnOff\Features\";
}
#endregion
}
}
......@@ -33,7 +33,9 @@ protected override object GetValueWorker(string keyName)
}
bool ISupportsNavigation.TryNavigateTo(bool isPreview)
=> DefinitionBucket.DefinitionItem.TryNavigateTo(Presenter._workspace, isPreview);
=> DefinitionBucket.DefinitionItem.TryNavigateTo(
Presenter._workspace,
isPreview ? NavigationBehavior.PreviewWithoutFocus : NavigationBehavior.Normal);
protected override IList<Inline> CreateLineTextInlines()
=> DefinitionBucket.DefinitionItem.DisplayParts
......
......@@ -53,7 +53,9 @@ private class RoslynDefinitionBucket : DefinitionBucket, ISupportsNavigation
}
public bool TryNavigateTo(bool isPreview)
=> DefinitionItem.TryNavigateTo(_presenter._workspace, isPreview);
=> DefinitionItem.TryNavigateTo(
_presenter._workspace,
isPreview ? NavigationBehavior.PreviewWithoutFocus : NavigationBehavior.Normal);
public override bool TryGetValue(string key, out object content)
{
......
......@@ -109,7 +109,7 @@ private class ExternalDefinitionItem : DefinitionItem
public override bool CanNavigateTo(Workspace workspace) => true;
public override bool TryNavigateTo(Workspace workspace, bool isPreview)
public override bool TryNavigateTo(Workspace workspace, NavigationBehavior _)
=> TryOpenFile() && TryNavigateToPosition();
private bool TryOpenFile()
......
......@@ -346,10 +346,17 @@ private IDisposable OpenNewDocumentStateScope(OptionSet options)
return null;
}
var state = __VSNEWDOCUMENTSTATE.NDS_Provisional;
// If we're just opening the provisional tab, then do not "activate" the document
// (i.e. don't give it focus). This way if a user is just arrowing through a set
// (i.e. don't give it focus) unless specifically requested.
// This way if a user is just arrowing through a set
// of FindAllReferences results, they don't have their cursor placed into the document.
var state = __VSNEWDOCUMENTSTATE.NDS_Provisional | __VSNEWDOCUMENTSTATE.NDS_NoActivate;
if (!options.GetOption(NavigationOptions.ActivateProvisionalTab))
{
state |= __VSNEWDOCUMENTSTATE.NDS_NoActivate;
}
return new NewDocumentStateScope(state, VSConstants.NewDocumentStateReason.Navigation);
}
}
......
......@@ -15,6 +15,7 @@
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
// Used in https://github.com/aspnet/AspNetCore-Tooling/tree/master/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/OOPTagHelperResolver.cs
[Obsolete("Use Microsoft.CodeAnalysis.ExternalAccess.Razor.RazorRemoteHostClient instead")]
internal sealed class RazorLanguageServiceClient
{
private readonly RemoteHostClient _client;
......
......@@ -2,6 +2,7 @@
// 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.CodeAnalysis;
......@@ -11,6 +12,7 @@
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
[Obsolete("Use Microsoft.CodeAnalysis.ExternalAccess.Razor.RazorRemoteHostClient instead")]
internal static class RazorLanguageServiceClientFactory
{
public static async Task<RazorLanguageServiceClient> CreateAsync(Workspace workspace, CancellationToken cancellationToken = default)
......
......@@ -3092,6 +3092,12 @@ public override SyntaxNode ArrayCreationExpression(SyntaxNode elementType, IEnum
public override SyntaxNode ObjectCreationExpression(SyntaxNode type, IEnumerable<SyntaxNode> arguments)
=> SyntaxFactory.ObjectCreationExpression((TypeSyntax)type, CreateArgumentList(arguments), null);
internal override SyntaxNode ObjectCreationExpression(SyntaxNode type, SyntaxToken openParen, SeparatedSyntaxList<SyntaxNode> arguments, SyntaxToken closeParen)
=> SyntaxFactory.ObjectCreationExpression(
(TypeSyntax)type,
SyntaxFactory.ArgumentList(openParen, arguments, closeParen),
initializer: null);
private static ArgumentListSyntax CreateArgumentList(IEnumerable<SyntaxNode> arguments)
=> SyntaxFactory.ArgumentList(CreateArguments(arguments));
......
......@@ -1960,6 +1960,9 @@ public SyntaxNode MemberAccessExpression(SyntaxNode expression, string memberNam
/// </summary>
public abstract SyntaxNode ObjectCreationExpression(SyntaxNode namedType, IEnumerable<SyntaxNode> arguments);
internal abstract SyntaxNode ObjectCreationExpression(
SyntaxNode namedType, SyntaxToken openParen, SeparatedSyntaxList<SyntaxNode> arguments, SyntaxToken closeParen);
/// <summary>
/// Creates an object creation expression.
/// </summary>
......
......@@ -726,7 +726,7 @@ private async Task AddDocumentsWithPotentialConflictsAsync(IEnumerable<Document>
continue;
}
var info = await SyntaxTreeIndex.GetIndexAsync(document, CancellationToken.None).ConfigureAwait(false);
var info = await SyntaxTreeIndex.GetIndexAsync(document, _cancellationToken).ConfigureAwait(false);
if (info.ProbablyContainsEscapedIdentifier(_originalText))
{
_documentsIdsToBeCheckedForConflict.Add(document.Id);
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.ConvertTupleToStruct;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Remote
{
// root level service for all Roslyn services
internal partial class CodeAnalysisService : IRemoteConvertTupleToStructCodeRefactoringProvider
{
public Task<SerializableConvertTupleToStructResult> ConvertToStructAsync(
PinnedSolutionInfo solutionInfo,
DocumentId documentId,
TextSpan span,
Scope scope,
CancellationToken cancellationToken)
{
return RunServiceAsync<SerializableConvertTupleToStructResult>(async () =>
{
using (UserOperationBooster.Boost())
{
var solution = await GetSolutionAsync(solutionInfo, cancellationToken).ConfigureAwait(false);
var document = solution.GetDocument(documentId);
var service = document.GetLanguageService<IConvertTupleToStructCodeRefactoringProvider>();
var updatedSolution = await service.ConvertToStructAsync(document, span, scope, cancellationToken).ConfigureAwait(false);
var cleanedSolution = await CleanupAsync(solution, updatedSolution, cancellationToken).ConfigureAwait(false);
var documentTextChanges = await RemoteUtilities.GetDocumentTextChangesAsync(
solution, cleanedSolution, cancellationToken).ConfigureAwait(false);
var renamedToken = await GetRenamedTokenAsync(
solution, cleanedSolution, cancellationToken).ConfigureAwait(false);
return new SerializableConvertTupleToStructResult
{
DocumentTextChanges = documentTextChanges,
RenamedToken = renamedToken,
};
}
}, cancellationToken);
}
private async Task<(DocumentId, TextSpan)> GetRenamedTokenAsync(
Solution oldSolution, Solution newSolution, CancellationToken cancellationToken)
{
var changes = newSolution.GetChangedDocuments(oldSolution);
foreach (var docId in changes)
{
var document = newSolution.GetDocument(docId);
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var renamedToken = root.GetAnnotatedTokens(RenameAnnotation.Kind).FirstOrNull();
if (renamedToken == null)
continue;
return (docId, renamedToken.Value.Span);
}
throw ExceptionUtilities.Unreachable;
}
private async Task<Solution> CleanupAsync(Solution oldSolution, Solution newSolution, CancellationToken cancellationToken)
{
var changes = newSolution.GetChangedDocuments(oldSolution);
var final = newSolution;
foreach (var docId in changes)
{
var cleaned = await CodeAction.CleanupDocumentAsync(
newSolution.GetDocument(docId), cancellationToken).ConfigureAwait(false);
var cleanedRoot = await cleaned.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
final = final.WithDocumentSyntaxRoot(docId, cleanedRoot);
}
return final;
}
}
}
......@@ -486,10 +486,12 @@ internal enum FunctionId
ChangeSignature_Data = 400,
DependentTypeFinder_FindAndCacheDerivedClassesAsync = 410,
DependentTypeFinder_FindAndCacheDerivedInterfacesAsync = 411,
DependentTypeFinder_FindAndCacheImplementingTypesAsync = 412,
AbstractEncapsulateFieldService_EncapsulateFieldsAsync = 410,
AbstractEncapsulateFieldService_EncapsulateFieldsAsync = 420,
AbstractConvertTupleToStructCodeRefactoringProvider_ConvertToStructAsync = 420,
DependentTypeFinder_FindAndCacheDerivedClassesAsync = 430,
DependentTypeFinder_FindAndCacheDerivedInterfacesAsync = 431,
DependentTypeFinder_FindAndCacheImplementingTypesAsync = 432,
}
}
......@@ -352,10 +352,18 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration
Public Overloads Overrides Function ObjectCreationExpression(typeName As SyntaxNode, arguments As IEnumerable(Of SyntaxNode)) As SyntaxNode
Return SyntaxFactory.ObjectCreationExpression(
Nothing,
attributeLists:=Nothing,
DirectCast(typeName, TypeSyntax),
CreateArgumentList(arguments),
Nothing)
initializer:=Nothing)
End Function
Friend Overrides Function ObjectCreationExpression(typeName As SyntaxNode, openParen As SyntaxToken, arguments As SeparatedSyntaxList(Of SyntaxNode), closeParen As SyntaxToken) As SyntaxNode
Return SyntaxFactory.ObjectCreationExpression(
attributeLists:=Nothing,
DirectCast(typeName, TypeSyntax),
SyntaxFactory.ArgumentList(openParen, arguments, closeParen),
initializer:=Nothing)
End Function
Public Overrides Function QualifiedName(left As SyntaxNode, right As SyntaxNode) As SyntaxNode
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册