|
// 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;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.CSharp.Simplification;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Snippets;
using Microsoft.CodeAnalysis.Snippets.SnippetProviders;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Snippets;
using static SyntaxFactory;
internal abstract class AbstractCSharpForLoopSnippetProvider : AbstractForLoopSnippetProvider<ForStatementSyntax>
{
private static readonly string[] s_iteratorBaseNames = ["i", "j", "k"];
protected abstract SyntaxKind ConditionKind { get; }
protected abstract SyntaxKind IncrementorKind { get; }
protected abstract ExpressionSyntax GenerateInitializerValue(SyntaxGenerator generator, SyntaxNode? inlineExpression);
protected abstract ExpressionSyntax GenerateRightSideOfCondition(SyntaxGenerator generator, SyntaxNode? inlineExpression);
protected abstract void AddSpecificPlaceholders(MultiDictionary<string, int> placeholderBuilder, ExpressionSyntax initializer, ExpressionSyntax rightOfCondition);
protected override bool CanInsertStatementAfterToken(SyntaxToken token)
=> token.IsBeginningOfStatementContext() || token.IsBeginningOfGlobalStatementContext();
protected override ValueTask<ForStatementSyntax> AdjustSnippetExpressionAsync(
Document document, ForStatementSyntax snippetExpressionNode, CancellationToken cancellationToken)
{
var editor = new SyntaxEditor(snippetExpressionNode, document.Project.Solution.Services);
foreach (var node in snippetExpressionNode.Declaration!.DescendantNodesAndSelf().Reverse())
editor.ReplaceNode(node, (node, _) => node.WithAdditionalAnnotations(Simplifier.Annotation));
return new((ForStatementSyntax)editor.GetChangedRoot());
}
protected override ForStatementSyntax GenerateStatement(
SyntaxGenerator generator, SyntaxContext syntaxContext, SimplifierOptions simplifierOptions, InlineExpressionInfo? inlineExpressionInfo)
{
var semanticModel = syntaxContext.SemanticModel;
var compilation = semanticModel.Compilation;
var csharpSimplifierOptions = (CSharpSimplifierOptions)simplifierOptions;
var iteratorName = NameGenerator.GenerateUniqueName(s_iteratorBaseNames, n => semanticModel.LookupSymbols(syntaxContext.Position, name: n).IsEmpty);
var iteratorVariable = generator.Identifier(iteratorName);
var indexVariable = (ExpressionSyntax)generator.IdentifierName(iteratorName);
var (iteratorType, inlineExpression) = GetLoopHeaderParts();
var variableDeclaration = VariableDeclaration(
iteratorType,
variables: [VariableDeclarator(iteratorVariable,
argumentList: null,
EqualsValueClause(GenerateInitializerValue(generator, inlineExpression)))])
.NormalizeWhitespace();
return ForStatement(
variableDeclaration,
initializers: [],
BinaryExpression(ConditionKind, indexVariable, GenerateRightSideOfCondition(generator, inlineExpression)),
[PostfixUnaryExpression(IncrementorKind, indexVariable)],
Block());
(TypeSyntax iteratorType, SyntaxNode? inlineExpression) GetLoopHeaderParts()
{
var inlineExpression = inlineExpressionInfo?.Node.WithoutLeadingTrivia();
if (inlineExpressionInfo is null)
{
// Explicit emit 'var' if the user has asked for "var for built-in types" option. We do that as the
// implicit 'allowVar' value passed to GenerateTypeSyntax doesn't work here as the inlineExpression will
// be in error, and thus the simplifier will not approve converting 'int' to 'var' in that scenario.
var iteratorType = csharpSimplifierOptions.VarForBuiltInTypes.Value
? IdentifierName("var")
: compilation.GetSpecialType(SpecialType.System_Int32).GenerateTypeSyntax();
return (iteratorType, inlineExpression);
}
var inlineExpressionType = inlineExpressionInfo.TypeInfo.Type;
Debug.Assert(inlineExpressionType is not null);
if (IsSuitableIntegerType(inlineExpressionType))
return (inlineExpressionType.GenerateTypeSyntax(), inlineExpression);
var property = FindLengthProperty(inlineExpressionType, compilation) ?? FindCountProperty(inlineExpressionType, compilation);
Contract.ThrowIfNull(property);
return (property.Type.GenerateTypeSyntax(), generator.MemberAccessExpression(inlineExpression, property.Name));
}
}
protected override ValueTask<ImmutableArray<SnippetPlaceholder>> GetPlaceHolderLocationsListAsync(
Document document, ForStatementSyntax forStatement, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken)
{
using var _ = ArrayBuilder<SnippetPlaceholder>.GetInstance(out var result);
var placeholderBuilder = new MultiDictionary<string, int>();
var declaration = forStatement.Declaration;
var condition = forStatement.Condition;
var incrementor = forStatement.Incrementors.Single();
var variableDeclarator = declaration!.Variables.Single();
var declaratorIdentifier = variableDeclarator.Identifier;
placeholderBuilder.Add(declaratorIdentifier.ValueText, declaratorIdentifier.SpanStart);
var conditionExpression = (BinaryExpressionSyntax)condition!;
var left = conditionExpression.Left;
placeholderBuilder.Add(left.ToString(), left.SpanStart);
AddSpecificPlaceholders(placeholderBuilder, variableDeclarator.Initializer!.Value, conditionExpression.Right);
var operand = ((PostfixUnaryExpressionSyntax)incrementor!).Operand;
placeholderBuilder.Add(operand.ToString(), operand.SpanStart);
foreach (var (key, value) in placeholderBuilder)
result.Add(new(key, [.. value]));
return new(result.ToImmutableAndClear());
}
protected override int GetTargetCaretPosition(ForStatementSyntax forStatement, SourceText sourceText)
=> CSharpSnippetHelpers.GetTargetCaretPositionInBlock(
forStatement,
static s => (BlockSyntax)s.Statement,
sourceText);
protected override Task<Document> AddIndentationToDocumentAsync(Document document, ForStatementSyntax forStatement, CancellationToken cancellationToken)
=> CSharpSnippetHelpers.AddBlockIndentationToDocumentAsync(
document,
forStatement,
static s => (BlockSyntax)s.Statement,
cancellationToken);
}
|