Skip to content

Commit

Permalink
Semantic snippets: Add inline statement snippets (#67819)
Browse files Browse the repository at this point in the history
* Add notion of inline statement snippets. Make `if` and `while` snippets inline statement snippets

* Add `AbstractSingleChangeSnippetProvider`

* Inlinify `foreach` snippet

* Return new line back
  • Loading branch information
DoctorKrolic authored May 1, 2023
1 parent c057f25 commit e25732e
Show file tree
Hide file tree
Showing 17 changed files with 308 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -406,5 +406,66 @@ public void Method()

await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}

[WpfFact]
public async Task InsertInlineSnippetForCorrectTypeTest()
{
var markupBeforeCommit = """
class Program
{
void M(bool arg)
{
arg.$$
}
}
""";

var expectedCodeAfterCommit = $$"""
class Program
{
void M(bool arg)
{
{{ItemToCommit}} (arg)
{
$$
}
}
}
""";

await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}

[WpfFact]
public async Task NoInlineSnippetForIncorrectTypeTest()
{
var markupBeforeCommit = """
class Program
{
void M(int arg)
{
arg.$$
}
}
""";

await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit);
}

[WpfFact]
public async Task NoInlineSnippetWhenNotDirectlyExpressionStatementTest()
{
var markupBeforeCommit = """
class Program
{
void M(bool arg)
{
System.Console.WriteLine(arg.$$);
}
}
""";

await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@
// 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.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
Expand Down Expand Up @@ -89,7 +85,7 @@ public async Task InsertForEachSnippetInGlobalContextTest()
}

[WpfFact]
public async Task InserForEachSnippetInConstructorTest()
public async Task InsertForEachSnippetInConstructorTest()
{
var markupBeforeCommit =
@"class Program
Expand All @@ -115,7 +111,7 @@ public Program()
}

[WpfFact]
public async Task InserForEachSnippetWithCollectionTest()
public async Task InsertForEachSnippetWithCollectionTest()
{
var markupBeforeCommit =
@"using System;
Expand Down Expand Up @@ -251,5 +247,73 @@ public async Task InsertForEachSnippetInParenthesizedLambdaExpressionScriptTest(
};";
await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit, sourceCodeKind: SourceCodeKind.Script);
}

[WpfFact]
public async Task InsertInlineForEachSnippetForCorrectTypeTest()
{
var markupBeforeCommit = """
using System.Collections.Generic;

class C
{
void M(List<int> list)
{
list.$$
}
}
""";

var expectedCodeAfterCommit = """
using System.Collections.Generic;

class C
{
void M(List<int> list)
{
foreach (var item in list)
{
$$
}
}
}
""";

await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}

[WpfFact]
public async Task NoInlineForEachSnippetForIncorrectTypeTest()
{
var markupBeforeCommit = """
class Program
{
void M(int arg)
{
arg.$$
}
}
""";

await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit);
}

[WpfFact]
public async Task NoInlineForEachSnippetWhenNotDirectlyExpressionStatementTest()
{
var markupBeforeCommit = """
using System;
using System.Collections.Generic;

class Program
{
void M(List<int> list)
{
Console.WriteLine(list.$$);
}
}
""";

await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
Expand Down Expand Up @@ -63,11 +62,10 @@ protected override async Task<bool> IsValidSnippetLocationAsync(Document documen
return isAfterIfStatement && await base.IsValidSnippetLocationAsync(document, position, cancellationToken).ConfigureAwait(false);
}

protected override Task<ImmutableArray<TextChange>> GenerateSnippetTextChangesAsync(Document document, int position, CancellationToken cancellationToken)
protected override Task<TextChange> GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken)
{
var elseClause = SyntaxFactory.ElseClause(SyntaxFactory.Block());

return Task.FromResult(ImmutableArray.Create(new TextChange(TextSpan.FromBounds(position, position), elseClause.ToFullString())));
return Task.FromResult(new TextChange(TextSpan.FromBounds(position, position), elseClause.ToFullString()));
}

protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget, SourceText sourceText)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Indentation;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Snippets;
using Microsoft.CodeAnalysis.Snippets.SnippetProviders;
Expand All @@ -31,26 +33,28 @@ public CSharpForEachLoopSnippetProvider()
{
}

/// <summary>
/// Creates the foreach statement syntax.
/// Must be done in language specific file since there is no generic way to generate the syntax.
/// </summary>
protected override async Task<SyntaxNode> CreateForEachLoopStatementSyntaxAsync(Document document, int position, CancellationToken cancellationToken)
protected override SyntaxNode GenerateStatement(SyntaxGenerator generator, SyntaxContext syntaxContext, SyntaxNode? inlineExpression)
{
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var semanticModel = syntaxContext.SemanticModel;
var position = syntaxContext.Position;

var varIdentifier = SyntaxFactory.IdentifierName("var");
var enumerationSymbol = semanticModel.LookupSymbols(position).FirstOrDefault(symbol => symbol.GetSymbolType() != null &&
symbol.GetSymbolType()!.AllInterfaces.Any(
namedSymbol => namedSymbol.SpecialType is SpecialType.System_Collections_Generic_IEnumerable_T or SpecialType.System_Collections_IEnumerable) &&
var collectionIdentifier = (ExpressionSyntax?)inlineExpression;

if (collectionIdentifier is null)
{
var enumerationSymbol = semanticModel.LookupSymbols(position).FirstOrDefault(symbol => symbol.GetSymbolType() is { } symbolType &&
symbolType.CanBeEnumerated() &&
symbol.Kind is SymbolKind.Local or SymbolKind.Field or SymbolKind.Parameter or SymbolKind.Property);
var collectionIdentifier = enumerationSymbol is null
? SyntaxFactory.IdentifierName("collection")
: SyntaxFactory.IdentifierName(enumerationSymbol.Name);
collectionIdentifier = enumerationSymbol is null
? SyntaxFactory.IdentifierName("collection")
: SyntaxFactory.IdentifierName(enumerationSymbol.Name);
}

var itemString = NameGenerator.GenerateUniqueName(
"item", name => semanticModel.LookupSymbols(position, name: name).IsEmpty);
var foreachLoopSyntax = SyntaxFactory.ForEachStatement(varIdentifier, itemString, collectionIdentifier, SyntaxFactory.Block());

return foreachLoopSyntax;
return SyntaxFactory.ForEachStatement(varIdentifier, itemString, collectionIdentifier, SyntaxFactory.Block()).NormalizeWhitespace();
}

/// <summary>
Expand All @@ -62,7 +66,9 @@ protected override ImmutableArray<SnippetPlaceholder> GetPlaceHolderLocationsLis
using var _ = ArrayBuilder<SnippetPlaceholder>.GetInstance(out var arrayBuilder);
GetPartsOfForEachStatement(node, out var identifier, out var expression, out var _1);
arrayBuilder.Add(new SnippetPlaceholder(identifier.ToString(), ImmutableArray.Create(identifier.SpanStart)));
arrayBuilder.Add(new SnippetPlaceholder(expression.ToString(), ImmutableArray.Create(expression.SpanStart)));

if (!ConstructedFromInlineExpression)
arrayBuilder.Add(new SnippetPlaceholder(expression.ToString(), ImmutableArray.Create(expression.SpanStart)));

return arrayBuilder.ToImmutableArray();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,29 @@

using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.Snippets.SnippetProviders
{
/// <summary>
/// Base class for "if" and "while" snippet providers
/// </summary>
internal abstract class AbstractConditionalBlockSnippetProvider : AbstractStatementSnippetProvider
internal abstract class AbstractConditionalBlockSnippetProvider : AbstractInlineStatementSnippetProvider
{
protected abstract TextChange GenerateSnippetTextChange(Document document, int position);
protected abstract SyntaxNode GetCondition(SyntaxNode node);

protected override Task<ImmutableArray<TextChange>> GenerateSnippetTextChangesAsync(Document document, int position, CancellationToken cancellationToken)
{
var snippetTextChange = GenerateSnippetTextChange(document, position);
return Task.FromResult(ImmutableArray.Create(snippetTextChange));
}
protected override bool IsValidAccessingType(ITypeSymbol type)
=> type.SpecialType == SpecialType.System_Boolean;

protected override ImmutableArray<SnippetPlaceholder> GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken)
{
using var _ = ArrayBuilder<SnippetPlaceholder>.GetInstance(out var arrayBuilder);
if (ConstructedFromInlineExpression)
return ImmutableArray<SnippetPlaceholder>.Empty;

var condition = GetCondition(node);
arrayBuilder.Add(new SnippetPlaceholder(identifier: condition.ToString(), placeholderPositions: ImmutableArray.Create(condition.SpanStart)));
var placeholder = new SnippetPlaceholder(identifier: condition.ToString(), placeholderPositions: ImmutableArray.Create(condition.SpanStart));

return arrayBuilder.ToImmutableArray();
return ImmutableArray.Create(placeholder);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,15 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Snippets;
using Microsoft.CodeAnalysis.Snippets.SnippetProviders;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
Expand Down Expand Up @@ -45,18 +39,12 @@ protected override async Task<bool> IsValidSnippetLocationAsync(Document documen
return await base.IsValidSnippetLocationAsync(document, position, cancellationToken).ConfigureAwait(false);
}

protected override async Task<ImmutableArray<TextChange>> GenerateSnippetTextChangesAsync(Document document, int position, CancellationToken cancellationToken)
{
var snippetTextChange = await GenerateSnippetTextChangeAsync(document, position, cancellationToken).ConfigureAwait(false);
return ImmutableArray.Create(snippetTextChange);
}

protected override Func<SyntaxNode?, bool> GetSnippetContainerFunction(ISyntaxFacts syntaxFacts)
{
return syntaxFacts.IsExpressionStatement;
}

private async Task<TextChange> GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken)
protected override async Task<TextChange> GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken)
{
var generator = SyntaxGenerator.GetGenerator(document);
var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editing;
Expand All @@ -16,7 +14,7 @@

namespace Microsoft.CodeAnalysis.Snippets.SnippetProviders
{
internal abstract class AbstractConstructorSnippetProvider : AbstractSnippetProvider
internal abstract class AbstractConstructorSnippetProvider : AbstractSingleChangeSnippetProvider
{
public override string Identifier => "ctor";

Expand All @@ -28,7 +26,7 @@ internal abstract class AbstractConstructorSnippetProvider : AbstractSnippetProv
protected override ImmutableArray<SnippetPlaceholder> GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken)
=> ImmutableArray<SnippetPlaceholder>.Empty;

protected override async Task<ImmutableArray<TextChange>> GenerateSnippetTextChangesAsync(Document document, int position, CancellationToken cancellationToken)
protected override async Task<TextChange> GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken)
{
var generator = SyntaxGenerator.GetGenerator(document);
var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
Expand All @@ -39,7 +37,7 @@ protected override async Task<ImmutableArray<TextChange>> GenerateSnippetTextCha
var constructorDeclaration = generator.ConstructorDeclaration(
containingTypeName: syntaxFacts.GetIdentifierOfTypeDeclaration(containingType).ToString(),
accessibility: Accessibility.Public);
return ImmutableArray.Create(new TextChange(TextSpan.FromBounds(position, position), constructorDeclaration.NormalizeWhitespace().ToFullString()));
return new TextChange(TextSpan.FromBounds(position, position), constructorDeclaration.NormalizeWhitespace().ToFullString());
}
}
}
Loading

0 comments on commit e25732e

Please sign in to comment.