Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add value tracking service #51898

Merged
20 changes: 20 additions & 0 deletions src/EditorFeatures/Core/ValueTracking/IValueTrackingService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;

namespace Microsoft.CodeAnalysis.ValueTracking
{
internal interface IValueTrackingService : IWorkspaceService
{
Task<ImmutableArray<ValueTrackedItem>> TrackValueSourceAsync(Solution solution, ISymbol symbol, CancellationToken cancellationToken);
Task TrackValueSourceAsync(Solution solution, ISymbol symbol, ValueTrackingProgressCollector progressCollector, CancellationToken cancellationToken);

Task<ImmutableArray<ValueTrackedItem>> TrackValueSourceAsync(Solution solution, ValueTrackedItem previousTrackedItem, CancellationToken cancellationToken);
Task TrackValueSourceAsync(Solution solution, ValueTrackedItem previousTrackedItem, ValueTrackingProgressCollector progressCollector, CancellationToken cancellationToken);
}
}
20 changes: 20 additions & 0 deletions src/EditorFeatures/Core/ValueTracking/ValueTrackedItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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.ValueTracking
{
internal class ValueTrackedItem
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be a record?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, why not :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, can we use records in Roslyn yet?

Severity	Code	Description	Project	File	Line	Suppression State
Error	CS0518	Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported	Microsoft.CodeAnalysis.EditorFeatures (netcoreapp3.1)

{
public Location Location { get; }
public ISymbol Symbol { get; }

public ValueTrackedItem(
Location location,
ISymbol symbol)
{
Location = location;
Symbol = symbol;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;

namespace Microsoft.CodeAnalysis.ValueTracking
{
internal class ValueTrackingProgressCollector : IProgress<ValueTrackedItem>
{
private readonly object _lock = new();
private readonly Stack<ValueTrackedItem> _items = new();

public event EventHandler<ValueTrackedItem>? OnNewItem;

public void Report(ValueTrackedItem item)
{
lock (_lock)
{
_items.Push(item);
}

OnNewItem?.Invoke(null, item);
}

public ImmutableArray<ValueTrackedItem> GetItems()
{
lock (_lock)
{
return _items.ToImmutableArray();
}
}
}
}
116 changes: 116 additions & 0 deletions src/EditorFeatures/Core/ValueTracking/ValueTrackingService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Immutable;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.Utilities;

namespace Microsoft.CodeAnalysis.ValueTracking
{
[ExportWorkspaceService(typeof(IValueTrackingService)), Shared]
internal class ValueTrackingService : IValueTrackingService
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public ValueTrackingService()
{
}

public async Task<ImmutableArray<ValueTrackedItem>> TrackValueSourceAsync(
Solution solution,
ISymbol symbol,
CancellationToken cancellationToken)
{
var progressTracker = new ValueTrackingProgressCollector();
await TrackValueSourceAsync(solution, symbol, progressTracker, cancellationToken).ConfigureAwait(false);
return progressTracker.GetItems();
}

public async Task TrackValueSourceAsync(
Solution solution,
ISymbol symbol,
ValueTrackingProgressCollector progressCollector,
CancellationToken cancellationToken)
{
if (symbol
is IPropertySymbol
or IFieldSymbol
or ILocalSymbol
or IParameterSymbol)
{
// Add all initializations of the symbol. Those are not caught in
// the reference finder but should still show up in the tree
foreach (var syntaxRef in symbol.DeclaringSyntaxReferences)
{
var location = Location.Create(syntaxRef.SyntaxTree, syntaxRef.Span);
progressCollector.Report(new ValueTrackedItem(location, symbol));
}

var findReferenceProgressCollector = new FindReferencesProgress(progressCollector);
await SymbolFinder.FindReferencesAsync(
symbol, solution, findReferenceProgressCollector,
documents: null, FindReferencesSearchOptions.Default, cancellationToken).ConfigureAwait(false);
}
}

public async Task<ImmutableArray<ValueTrackedItem>> TrackValueSourceAsync(
Solution solution,
ValueTrackedItem previousTrackedItem,
CancellationToken cancellationToken)
{
var progressTracker = new ValueTrackingProgressCollector();
await TrackValueSourceAsync(solution, previousTrackedItem, progressTracker, cancellationToken).ConfigureAwait(false);
return progressTracker.GetItems();
}

public Task TrackValueSourceAsync(
Solution solution,
ValueTrackedItem previousTrackedItem,
ValueTrackingProgressCollector progressCollector,
CancellationToken cancellationToken)
{
throw new NotImplementedException();
}

private class FindReferencesProgress : IStreamingFindReferencesProgress, IStreamingProgressTracker
{
private readonly ValueTrackingProgressCollector _valueTrackingProgressCollector;
public FindReferencesProgress(ValueTrackingProgressCollector valueTrackingProgressCollector)
{
_valueTrackingProgressCollector = valueTrackingProgressCollector;
}

public IStreamingProgressTracker ProgressTracker => this;

public ValueTask AddItemsAsync(int count) => new();

public ValueTask ItemCompletedAsync() => new();

public ValueTask OnCompletedAsync() => new();

public ValueTask OnDefinitionFoundAsync(ISymbol symbol) => new();

public ValueTask OnFindInDocumentCompletedAsync(Document document) => new();

public ValueTask OnFindInDocumentStartedAsync(Document document) => new();

public ValueTask OnReferenceFoundAsync(ISymbol symbol, ReferenceLocation location)
{
if (location.IsWrittenTo)
{
_valueTrackingProgressCollector.Report(new ValueTrackedItem(location.Location, symbol));
}

return new();
}

public ValueTask OnStartedAsync() => new();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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.Threading.Tasks;
using Microsoft.CodeAnalysis.ValueTracking;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Shared.Extensions;
using System.Threading;
using Microsoft.CodeAnalysis.Text;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.UnitTests.ValueTracking
{
public abstract class AbstractBaseValueTrackingTests
{
internal static async Task<ImmutableArray<ValueTrackedItem>> GetTrackedItemsAsync(TestWorkspace testWorkspace, CancellationToken cancellationToken = default)
{
var cursorDocument = testWorkspace.DocumentWithCursor;
var document = testWorkspace.CurrentSolution.GetRequiredDocument(cursorDocument.Id);
var textSpan = new TextSpan(cursorDocument.CursorPosition!.Value, 0);
var symbol = await GetSelectedSymbolAsync(textSpan, document, cancellationToken);
var service = testWorkspace.Services.GetRequiredService<IValueTrackingService>();
return await service.TrackValueSourceAsync(testWorkspace.CurrentSolution, symbol, cancellationToken);

}

internal static async Task<ISymbol> GetSelectedSymbolAsync(TextSpan textSpan, Document document, CancellationToken cancellationToken)
{
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var selectedNode = root.FindNode(textSpan);

Assert.NotNull(selectedNode);

var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var selectedSymbol =
semanticModel.GetSymbolInfo(selectedNode, cancellationToken).Symbol
?? semanticModel.GetDeclaredSymbol(selectedNode, cancellationToken);

Assert.NotNull(selectedSymbol);
return selectedSymbol!;
}

internal static void ValidateItem(ValueTrackedItem item, int line)
{
var lineSpan = item.Location.GetLineSpan();
Assert.Equal(line, lineSpan.StartLinePosition.Line);
}
}
}
Loading