Skip to content

Commit

Permalink
Merge branch 'main' into EnCMoreDeletes
Browse files Browse the repository at this point in the history
  • Loading branch information
davidwengier authored Jun 28, 2022
2 parents 167091e + a301583 commit 2ef5582
Show file tree
Hide file tree
Showing 70 changed files with 840 additions and 702 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,69 +39,93 @@ public static async Task<Document> ConvertAsync(Document document, BaseNamespace
return await ConvertFileScopedNamespaceAsync(document, fileScopedNamespace, cancellationToken).ConfigureAwait(false);

case NamespaceDeclarationSyntax namespaceDeclaration:
var (doc, _) = await ConvertNamespaceDeclarationAsync(document, namespaceDeclaration, options, cancellationToken).ConfigureAwait(false);
return doc;
return await ConvertNamespaceDeclarationAsync(document, namespaceDeclaration, options, cancellationToken).ConfigureAwait(false);

default:
throw ExceptionUtilities.UnexpectedValue(baseNamespace.Kind());
}
}

public static async Task<(Document document, TextSpan semicolonSpan)> ConvertNamespaceDeclarationAsync(Document document, NamespaceDeclarationSyntax namespaceDeclaration, SyntaxFormattingOptions options, CancellationToken cancellationToken)
/// <summary>
/// Asynchrounous implementation for code fixes.
/// </summary>
public static async ValueTask<Document> ConvertNamespaceDeclarationAsync(Document document, NamespaceDeclarationSyntax namespaceDeclaration, SyntaxFormattingOptions options, CancellationToken cancellationToken)
{
// First, determine how much indentation we had inside the original block namespace. We'll attempt to remove
var parsedDocument = await ParsedDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false);

// Replace the block namespace with the file scoped namespace.
var annotation = new SyntaxAnnotation();
var (updatedRoot, _) = ReplaceWithFileScopedNamespace(parsedDocument, namespaceDeclaration, annotation);
var updatedDocument = document.WithSyntaxRoot(updatedRoot);

// Determine how much indentation we had inside the original block namespace. We'll attempt to remove
// that much indentation from each applicable line after we conver the block namespace to a file scoped
// namespace.
var indentation = GetIndentation(parsedDocument, namespaceDeclaration, options, cancellationToken);
if (indentation == null)
return updatedDocument;

var indentation = await GetIndentationAsync(document, namespaceDeclaration, options, cancellationToken).ConfigureAwait(false);
// Now, find the file scoped namespace in the updated doc and go and dedent every line if applicable.
var updatedParsedDocument = await ParsedDocument.CreateAsync(updatedDocument, cancellationToken).ConfigureAwait(false);
var (dedentedText, _) = DedentNamespace(updatedParsedDocument, indentation, annotation, cancellationToken);
return document.WithText(dedentedText);
}

// Next, actually replace the block namespace with the file scoped namespace.
/// <summary>
/// Synchronous implementation for a command handler.
/// </summary>
public static (SourceText text, TextSpan semicolonSpan) ConvertNamespaceDeclaration(ParsedDocument document, NamespaceDeclarationSyntax namespaceDeclaration, SyntaxFormattingOptions options, CancellationToken cancellationToken)
{
// Replace the block namespace with the file scoped namespace.
var annotation = new SyntaxAnnotation();
var (updatedDocument, semicolonSpan) = await ReplaceWithFileScopedNamespaceAsync(document, namespaceDeclaration, annotation, cancellationToken).ConfigureAwait(false);
var (updatedRoot, semicolonSpan) = ReplaceWithFileScopedNamespace(document, namespaceDeclaration, annotation);
var updatedDocument = document.WithChangedRoot(updatedRoot, cancellationToken);

// Now, find the file scoped namespace in the updated doc and go and dedent every line if applicable.
// Determine how much indentation we had inside the original block namespace. We'll attempt to remove
// that much indentation from each applicable line after we conver the block namespace to a file scoped
// namespace.

var indentation = GetIndentation(document, namespaceDeclaration, options, cancellationToken);
if (indentation == null)
return (updatedDocument, semicolonSpan);
return (updatedDocument.Text, semicolonSpan);

return await DedentNamespaceAsync(updatedDocument, indentation, annotation, cancellationToken).ConfigureAwait(false);
// Now, find the file scoped namespace in the updated doc and go and dedent every line if applicable.
return DedentNamespace(updatedDocument, indentation, annotation, cancellationToken);
}

private static async Task<(Document document, TextSpan semicolonSpan)> ReplaceWithFileScopedNamespaceAsync(
Document document, NamespaceDeclarationSyntax namespaceDeclaration, SyntaxAnnotation annotation, CancellationToken cancellationToken)
private static (SyntaxNode root, TextSpan semicolonSpan) ReplaceWithFileScopedNamespace(
ParsedDocument document, NamespaceDeclarationSyntax namespaceDeclaration, SyntaxAnnotation annotation)
{
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var converted = ConvertNamespaceDeclaration(namespaceDeclaration);
var updatedRoot = root.ReplaceNode(
var updatedRoot = document.Root.ReplaceNode(
namespaceDeclaration,
converted.WithAdditionalAnnotations(annotation));
var fileScopedNamespace = (FileScopedNamespaceDeclarationSyntax)updatedRoot.GetAnnotatedNodes(annotation).Single();
return (document.WithSyntaxRoot(updatedRoot), fileScopedNamespace.SemicolonToken.Span);
return (updatedRoot, fileScopedNamespace.SemicolonToken.Span);
}

private static async Task<string?> GetIndentationAsync(Document document, NamespaceDeclarationSyntax namespaceDeclaration, SyntaxFormattingOptions options, CancellationToken cancellationToken)
private static string? GetIndentation(ParsedDocument document, NamespaceDeclarationSyntax namespaceDeclaration, SyntaxFormattingOptions options, CancellationToken cancellationToken)
{
var indentationService = document.GetRequiredLanguageService<IIndentationService>();
var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

var openBraceLine = sourceText.Lines.GetLineFromPosition(namespaceDeclaration.OpenBraceToken.SpanStart).LineNumber;
var closeBraceLine = sourceText.Lines.GetLineFromPosition(namespaceDeclaration.CloseBraceToken.SpanStart).LineNumber;
var openBraceLine = document.Text.Lines.GetLineFromPosition(namespaceDeclaration.OpenBraceToken.SpanStart).LineNumber;
var closeBraceLine = document.Text.Lines.GetLineFromPosition(namespaceDeclaration.CloseBraceToken.SpanStart).LineNumber;
if (openBraceLine == closeBraceLine)
return null;

// Auto-formatting options are not relevant since they only control behavior on typing.
var indentationOptions = new IndentationOptions(options);

var indentationService = document.LanguageServices.GetRequiredService<IIndentationService>();
var indentation = indentationService.GetIndentation(document, openBraceLine + 1, indentationOptions, cancellationToken);

return indentation.GetIndentationString(sourceText, options.UseTabs, options.TabSize);
return indentation.GetIndentationString(document.Text, options.UseTabs, options.TabSize);
}

private static async Task<(Document document, TextSpan semicolonSpan)> DedentNamespaceAsync(
Document document, string indentation, SyntaxAnnotation annotation, CancellationToken cancellationToken)
private static (SourceText text, TextSpan semicolonSpan) DedentNamespace(
ParsedDocument document, string indentation, SyntaxAnnotation annotation, CancellationToken cancellationToken)
{
var syntaxTree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var text = await root.SyntaxTree.GetTextAsync(cancellationToken).ConfigureAwait(false);
var syntaxTree = document.SyntaxTree;
var text = document.Text;
var root = document.Root;

var fileScopedNamespace = (FileScopedNamespaceDeclarationSyntax)root.GetAnnotatedNodes(annotation).Single();
var semicolonLine = text.Lines.GetLineFromPosition(fileScopedNamespace.SemicolonToken.SpanStart).LineNumber;
Expand All @@ -111,7 +135,7 @@ public static async Task<Document> ConvertAsync(Document document, BaseNamespace
changes.AddIfNotNull(TryDedentLine(syntaxTree, text, indentation, text.Lines[line], cancellationToken));

var dedentedText = text.WithChanges(changes);
return (document.WithText(dedentedText), fileScopedNamespace.SemicolonToken.Span);
return (dedentedText, fileScopedNamespace.SemicolonToken.Span);
}

private static TextChange? TryDedentLine(
Expand Down
20 changes: 17 additions & 3 deletions src/Compilers/Core/Portable/SourceGeneration/Nodes/BatchNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis
Expand Down Expand Up @@ -49,9 +50,22 @@ public NodeStateTable<ImmutableArray<TInput>> UpdateStateTable(DriverStateTable.

var stopwatch = SharedStopwatch.StartNew();

var batchedSourceEntries = sourceTable.Batch();
var sourceValues = batchedSourceEntries.SelectAsArray(sourceEntry => sourceEntry.State != EntryState.Removed, sourceEntry => sourceEntry.Item);
var sourceInputs = newTable.TrackIncrementalSteps ? batchedSourceEntries.SelectAsArray(sourceEntry => (sourceEntry.Step!, sourceEntry.OutputIndex)) : default;
var sourceValuesBuilder = ArrayBuilder<TInput>.GetInstance();
var sourceInputsBuilder = newTable.TrackIncrementalSteps ? ArrayBuilder<(IncrementalGeneratorRunStep InputStep, int OutputIndex)>.GetInstance() : null;

foreach (var entry in sourceTable)
{
// At this point, we can remove any 'Removed' items and ensure they're not in our list of states.
if (entry.State != EntryState.Removed)
sourceValuesBuilder.Add(entry.Item);

// However, regardless of if the entry was removed or not, we still keep track of its step information
// so we can accurately report how long it took and what actually happened (for testing validation).
sourceInputsBuilder?.Add((entry.Step!, entry.OutputIndex));
}

var sourceValues = sourceValuesBuilder.ToImmutableAndFree();
var sourceInputs = newTable.TrackIncrementalSteps ? sourceInputsBuilder!.ToImmutableAndFree() : default;

if (previousTable.IsEmpty)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ internal sealed class NodeStateTable<T> : IStateTable

private readonly ImmutableArray<TableEntry> _states;


private NodeStateTable(ImmutableArray<TableEntry> states, ImmutableArray<IncrementalGeneratorRunStep> steps, bool isCompacted, bool hasTrackedSteps)
{
Debug.Assert(!isCompacted || states.All(s => s.IsCached));
Expand Down Expand Up @@ -128,25 +127,8 @@ public NodeStateTable<T> AsCached()
return (_states[^1].GetItem(0), HasTrackedSteps ? Steps[^1] : null);
}

public ImmutableArray<NodeStateEntry<T>> Batch()
{
var sourceBuilder = ArrayBuilder<NodeStateEntry<T>>.GetInstance();
foreach (var entry in this)
{
// If we have tracked steps, then we need to report removed entries to ensure all steps are in the graph.
// Otherwise, we can just return non-removed entries to build the next value.
if (entry.State != EntryState.Removed || HasTrackedSteps)
{
sourceBuilder.Add(entry);
}
}
return sourceBuilder.ToImmutableAndFree();
}

public Builder ToBuilder(string? stepName, bool stepTrackingEnabled)
{
return new Builder(this, stepName, stepTrackingEnabled);
}
=> new Builder(this, stepName, stepTrackingEnabled);

public NodeStateTable<T> CreateCachedTableWithUpdatedSteps<TInput>(NodeStateTable<TInput> inputTable, string? stepName)
{
Expand Down Expand Up @@ -447,7 +429,7 @@ public T GetItem(int index)

public TableEntry AsRemoved() => new(_item, _items, s_allRemovedEntries);

[MemberNotNullWhen(true, new[] { nameof(_item) })]
[MemberNotNullWhen(true, nameof(_item))]
private bool IsSingle => this._items.IsDefault;

private static ImmutableArray<EntryState> GetSingleArray(EntryState state) => state switch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
using Microsoft.VisualStudio.Text.Operations;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Utilities;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.VisualStudio.Text.Editor;

namespace Microsoft.CodeAnalysis.Editor.CSharp.CompleteStatement
{
Expand All @@ -50,17 +52,24 @@ internal sealed class ConvertNamespaceCommandHandler : IChainedCommandHandler<Ty

private readonly ITextUndoHistoryRegistry _textUndoHistoryRegistry;
private readonly IEditorOperationsFactoryService _editorOperationsFactoryService;
private readonly IEditorOptionsFactoryService _editorOptionsFactory;
private readonly IIndentationManagerService _indentationManager;
private readonly IGlobalOptionService _globalOptions;

[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public ConvertNamespaceCommandHandler(
ITextUndoHistoryRegistry textUndoHistoryRegistry,
IEditorOperationsFactoryService editorOperationsFactoryService, IGlobalOptionService globalOptions)
IEditorOperationsFactoryService editorOperationsFactoryService,
IEditorOptionsFactoryService editorOptionsFactory,
IGlobalOptionService globalOptions,
IIndentationManagerService indentationManager)
{
_textUndoHistoryRegistry = textUndoHistoryRegistry;
_editorOperationsFactoryService = editorOperationsFactoryService;
_editorOptionsFactory = editorOptionsFactory;
_globalOptions = globalOptions;
_indentationManager = indentationManager;
}

public CommandState GetCommandState(TypeCharCommandArgs args, Func<CommandState> nextCommandHandler)
Expand Down Expand Up @@ -122,10 +131,10 @@ public void ExecuteCommand(TypeCharCommandArgs args, Action nextCommandHandler,
return default;

var cancellationToken = executionContext.OperationContext.UserCancellationToken;
var root = (CompilationUnitSyntax)document.GetRequiredSyntaxRootSynchronously(cancellationToken);
var parsedDocument = ParsedDocument.CreateSynchronously(document, cancellationToken);

// User has to be *after* an identifier token.
var token = root.FindToken(caret);
var token = parsedDocument.Root.FindToken(caret);
if (token.Kind() != SyntaxKind.IdentifierToken)
return default;

Expand All @@ -144,13 +153,11 @@ public void ExecuteCommand(TypeCharCommandArgs args, Action nextCommandHandler,
return default;

// Pass in our special options, and C#10 so that if we can convert this to file-scoped, we will.
if (!ConvertNamespaceAnalysis.CanOfferUseFileScoped(s_fileScopedNamespacePreferenceOption, root, namespaceDecl, forAnalyzer: true, LanguageVersion.CSharp10))
if (!ConvertNamespaceAnalysis.CanOfferUseFileScoped(s_fileScopedNamespacePreferenceOption, (CompilationUnitSyntax)parsedDocument.Root, namespaceDecl, forAnalyzer: true, LanguageVersion.CSharp10))
return default;

var formattingOptions = document.GetSyntaxFormattingOptionsAsync(_globalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken);
var (converted, semicolonSpan) = ConvertNamespaceTransform.ConvertNamespaceDeclarationAsync(document, namespaceDecl, formattingOptions, cancellationToken).WaitAndGetResult(cancellationToken);
var text = converted.GetTextSynchronously(cancellationToken);
return (text, semicolonSpan);
var formattingOptions = subjectBuffer.GetSyntaxFormattingOptions(_editorOptionsFactory, _indentationManager, _globalOptions, document.Project.LanguageServices, explicitFormat: false);
return ConvertNamespaceTransform.ConvertNamespaceDeclaration(parsedDocument, namespaceDecl, formattingOptions, cancellationToken);
}
}
}
Loading

0 comments on commit 2ef5582

Please sign in to comment.