-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
No functional changes, just moving to the new API and cleaning up unused code
- Loading branch information
Showing
43 changed files
with
1,281 additions
and
791 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
112 changes: 112 additions & 0 deletions
112
src/EditorFeatures/Core/StackTraceExplorer/StackTraceExplorerService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Composition; | ||
using System.IO; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis.EmbeddedLanguages.StackFrame; | ||
using Microsoft.CodeAnalysis.FindUsages; | ||
using Microsoft.CodeAnalysis.Host.Mef; | ||
using Microsoft.CodeAnalysis.Remote; | ||
using Microsoft.CodeAnalysis.StackTraceExplorer; | ||
using Roslyn.Utilities; | ||
|
||
namespace Microsoft.CodeAnalysis.Editor.StackTraceExplorer | ||
{ | ||
[ExportWorkspaceService(typeof(IStackTraceExplorerService)), Shared] | ||
internal class StackTraceExplorerService : IStackTraceExplorerService | ||
{ | ||
[ImportingConstructor] | ||
[System.Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] | ||
public StackTraceExplorerService() | ||
{ | ||
} | ||
|
||
public (Document? document, int line) GetDocumentAndLine(Solution solution, ParsedFrame frame) | ||
{ | ||
if (frame is ParsedStackFrame parsedFrame) | ||
{ | ||
var matches = GetFileMatches(solution, parsedFrame.Root, out var line); | ||
if (matches.IsEmpty) | ||
{ | ||
return default; | ||
} | ||
|
||
return (matches[0], line); | ||
} | ||
|
||
return default; | ||
} | ||
|
||
public async Task<DefinitionItem?> TryFindDefinitionAsync(Solution solution, ParsedFrame frame, StackFrameSymbolPart symbolPart, CancellationToken cancellationToken) | ||
{ | ||
if (frame is not ParsedStackFrame parsedFrame) | ||
{ | ||
return null; | ||
} | ||
|
||
var client = await RemoteHostClient.TryGetClientAsync(solution.Workspace, cancellationToken).ConfigureAwait(false); | ||
if (client is not null) | ||
{ | ||
var result = await client.TryInvokeAsync<IRemoteStackTraceExplorerService, SerializableDefinitionItem?>( | ||
solution, | ||
(service, solutionInfo, cancellationToken) => service.TryFindDefinitionAsync(solutionInfo, parsedFrame.ToString(), symbolPart, cancellationToken), | ||
cancellationToken).ConfigureAwait(false); | ||
|
||
if (!result.HasValue) | ||
{ | ||
return null; | ||
} | ||
|
||
var serializedDefinition = result.Value; | ||
if (!serializedDefinition.HasValue) | ||
{ | ||
return null; | ||
} | ||
|
||
return await serializedDefinition.Value.RehydrateAsync(solution, cancellationToken).ConfigureAwait(false); | ||
} | ||
|
||
return await StackTraceExplorerUtilities.GetDefinitionAsync(solution, parsedFrame.Root, symbolPart, cancellationToken).ConfigureAwait(false); | ||
} | ||
|
||
private static ImmutableArray<Document> GetFileMatches(Solution solution, StackFrameCompilationUnit root, out int lineNumber) | ||
{ | ||
lineNumber = 0; | ||
if (root.FileInformationExpression is null) | ||
{ | ||
return ImmutableArray<Document>.Empty; | ||
} | ||
|
||
var fileName = root.FileInformationExpression.Path.ToString(); | ||
var lineString = root.FileInformationExpression.Line.ToString(); | ||
RoslynDebug.AssertNotNull(lineString); | ||
lineNumber = int.Parse(lineString); | ||
|
||
var documentName = Path.GetFileName(fileName); | ||
var potentialMatches = new HashSet<Document>(); | ||
|
||
foreach (var project in solution.Projects) | ||
{ | ||
foreach (var document in project.Documents) | ||
{ | ||
if (document.FilePath == fileName) | ||
{ | ||
return ImmutableArray.Create(document); | ||
} | ||
|
||
else if (document.Name == documentName) | ||
{ | ||
potentialMatches.Add(document); | ||
} | ||
} | ||
} | ||
|
||
return potentialMatches.ToImmutableArray(); | ||
} | ||
} | ||
} |
207 changes: 207 additions & 0 deletions
207
src/EditorFeatures/Core/StackTraceExplorer/StackTraceExplorerUtilities.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis.Editor.FindUsages; | ||
using Microsoft.CodeAnalysis.EmbeddedLanguages.StackFrame; | ||
using Microsoft.CodeAnalysis.FindUsages; | ||
using Microsoft.CodeAnalysis.Shared.Extensions; | ||
using Microsoft.CodeAnalysis.StackTraceExplorer; | ||
using Roslyn.Utilities; | ||
|
||
namespace Microsoft.CodeAnalysis.Editor.StackTraceExplorer | ||
{ | ||
internal static class StackTraceExplorerUtilities | ||
{ | ||
public static async Task<DefinitionItem?> GetDefinitionAsync(Solution solution, StackFrameCompilationUnit compilationUnit, StackFrameSymbolPart symbolPart, CancellationToken cancellationToken) | ||
{ | ||
// MemberAccessExpression is [Expression].[Identifier], and Identifier is the | ||
// method name. | ||
var typeExpression = compilationUnit.MethodDeclaration.MemberAccessExpression.Left; | ||
|
||
// typeExpression.ToString() returns the full expression (or identifier) | ||
// including arity for generic types. | ||
var fullyQualifiedTypeName = typeExpression.ToString(); | ||
|
||
var typeName = typeExpression is StackFrameQualifiedNameNode qualifiedName | ||
? qualifiedName.Right.ToString() | ||
: typeExpression.ToString(); | ||
|
||
RoslynDebug.AssertNotNull(fullyQualifiedTypeName); | ||
|
||
var methodIdentifier = compilationUnit.MethodDeclaration.MemberAccessExpression.Right; | ||
var methodTypeArguments = compilationUnit.MethodDeclaration.TypeArguments; | ||
var methodArguments = compilationUnit.MethodDeclaration.ArgumentList; | ||
|
||
var methodName = methodIdentifier.ToString(); | ||
|
||
// | ||
// Do a first pass to find projects with the type name to check first | ||
// | ||
using var _ = PooledObjects.ArrayBuilder<Project>.GetInstance(out var candidateProjects); | ||
foreach (var project in solution.Projects) | ||
{ | ||
if (!project.SupportsCompilation) | ||
{ | ||
continue; | ||
} | ||
|
||
var containsSymbol = await project.ContainsSymbolsWithNameAsync( | ||
typeName, | ||
SymbolFilter.Type, | ||
cancellationToken).ConfigureAwait(false); | ||
|
||
if (containsSymbol) | ||
{ | ||
var matchingMethods = await GetMatchingMembersFromCompilationAsync(project).ConfigureAwait(false); | ||
if (matchingMethods.Any()) | ||
{ | ||
return await GetDefinitionAsync(matchingMethods[0]).ConfigureAwait(false); | ||
} | ||
} | ||
else | ||
{ | ||
candidateProjects.Add(project); | ||
} | ||
} | ||
|
||
// | ||
// Do a second pass to check the remaining compilations | ||
// for the symbol, which may be a metadata symbol in the compilation | ||
// | ||
foreach (var project in candidateProjects) | ||
{ | ||
var matchingMethods = await GetMatchingMembersFromCompilationAsync(project).ConfigureAwait(false); | ||
if (matchingMethods.Any()) | ||
{ | ||
return await GetDefinitionAsync(matchingMethods[0]).ConfigureAwait(false); | ||
} | ||
} | ||
|
||
return null; | ||
|
||
// | ||
// Local Functions | ||
// | ||
|
||
async Task<ImmutableArray<IMethodSymbol>> GetMatchingMembersFromCompilationAsync(Project project) | ||
{ | ||
var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); | ||
var type = compilation.GetTypeByMetadataName(fullyQualifiedTypeName); | ||
if (type is null) | ||
{ | ||
return ImmutableArray<IMethodSymbol>.Empty; | ||
} | ||
|
||
var members = type.GetMembers(); | ||
return members | ||
.OfType<IMethodSymbol>() | ||
.Where(m => m.Name == methodName) | ||
.Where(m => MatchTypeArguments(m.TypeArguments, methodTypeArguments)) | ||
.Where(m => MatchParameters(m.Parameters, methodArguments)) | ||
.ToImmutableArrayOrEmpty(); | ||
} | ||
|
||
Task<DefinitionItem> GetDefinitionAsync(IMethodSymbol method) | ||
{ | ||
ISymbol symbol = method; | ||
if (symbolPart == StackFrameSymbolPart.ContainingType) | ||
{ | ||
symbol = method.ContainingType; | ||
} | ||
|
||
return symbol.ToNonClassifiedDefinitionItemAsync(solution, includeHiddenLocations: true, cancellationToken); | ||
} | ||
} | ||
|
||
private static bool MatchParameters(ImmutableArray<IParameterSymbol> parameters, StackFrameParameterList stackFrameParameters) | ||
{ | ||
if (parameters.Length != stackFrameParameters.Parameters.Length) | ||
{ | ||
return false; | ||
} | ||
|
||
for (var i = 0; i < stackFrameParameters.Parameters.Length; i++) | ||
{ | ||
var stackFrameParameter = stackFrameParameters.Parameters[i]; | ||
var paramSymbol = parameters[i]; | ||
|
||
if (paramSymbol.Name != stackFrameParameter.Identifier.ToString()) | ||
{ | ||
return false; | ||
} | ||
|
||
if (!MatchType(paramSymbol.Type, stackFrameParameter.Type)) | ||
{ | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private static bool MatchTypeArguments(ImmutableArray<ITypeSymbol> typeArguments, StackFrameTypeArgumentList? stackFrameTypeArgumentList) | ||
{ | ||
if (stackFrameTypeArgumentList is null) | ||
{ | ||
return typeArguments.IsEmpty; | ||
} | ||
|
||
if (typeArguments.IsEmpty) | ||
{ | ||
return false; | ||
} | ||
|
||
var stackFrameTypeArguments = stackFrameTypeArgumentList.TypeArguments; | ||
return typeArguments.Length == stackFrameTypeArguments.Length; | ||
} | ||
|
||
private static bool MatchType(ITypeSymbol type, StackFrameTypeNode stackFrameType) | ||
{ | ||
if (type is IArrayTypeSymbol arrayType) | ||
{ | ||
if (stackFrameType is not StackFrameArrayTypeNode arrayTypeNode) | ||
{ | ||
return false; | ||
} | ||
|
||
ITypeSymbol currentType = arrayType; | ||
|
||
// Iterate through each array expression and make sure the dimensions | ||
// match the element types in an array. | ||
// Ex: string[,][] | ||
// [,] is a 2 dimension array with element type string[] | ||
// [] is a 1 dimension array with element type string | ||
foreach (var arrayExpression in arrayTypeNode.ArrayRankSpecifiers) | ||
{ | ||
if (currentType is not IArrayTypeSymbol currentArrayType) | ||
{ | ||
return false; | ||
} | ||
|
||
if (currentArrayType.Rank != arrayExpression.CommaTokens.Length + 1) | ||
{ | ||
return false; | ||
} | ||
|
||
currentType = currentArrayType.ElementType; | ||
} | ||
|
||
// All array types have been exchausted from the | ||
// stackframe identifier and the type is still an array | ||
if (currentType is IArrayTypeSymbol) | ||
{ | ||
return false; | ||
} | ||
|
||
return MatchType(currentType, arrayTypeNode.TypeIdentifier); | ||
} | ||
|
||
return type.Name == stackFrameType.ToString(); | ||
} | ||
} | ||
} |
Oops, something went wrong.