|
// 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 Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace Microsoft.CodeAnalysis.CSharp.Formatting;
internal sealed class QueryExpressionFormattingRule : BaseFormattingRule
{
internal const string Name = "CSharp Query Expressions Formatting Rule";
private readonly CSharpSyntaxFormattingOptions _options;
public QueryExpressionFormattingRule()
: this(CSharpSyntaxFormattingOptions.Default)
{
}
private QueryExpressionFormattingRule(CSharpSyntaxFormattingOptions options)
{
_options = options;
}
public override AbstractFormattingRule WithOptions(SyntaxFormattingOptions options)
{
var newOptions = options as CSharpSyntaxFormattingOptions ?? CSharpSyntaxFormattingOptions.Default;
if (_options.NewLines.HasFlag(NewLinePlacement.BetweenQueryExpressionClauses) == newOptions.NewLines.HasFlag(NewLinePlacement.BetweenQueryExpressionClauses))
{
return this;
}
return new QueryExpressionFormattingRule(newOptions);
}
public override void AddSuppressOperations(ArrayBuilder<SuppressOperation> list, SyntaxNode node, in NextSuppressOperationAction nextOperation)
{
nextOperation.Invoke();
if (node is QueryExpressionSyntax queryExpression)
{
AddSuppressWrappingIfOnSingleLineOperation(list, queryExpression.GetFirstToken(includeZeroWidth: true), queryExpression.GetLastToken(includeZeroWidth: true));
}
}
private static void AddIndentBlockOperationsForFromClause(List<IndentBlockOperation> list, FromClauseSyntax fromClause)
{
// Only add the indent block operation if the 'in' keyword is present. Otherwise, we'll get the following:
//
// from x
// in args
//
// Rather than:
//
// from x
// in args
//
// However, we want to get the following result if the 'in' keyword is present to allow nested queries
// to be formatted properly.
//
// from x in
// args
if (fromClause.InKeyword.IsMissing)
{
return;
}
var baseToken = fromClause.FromKeyword;
var startToken = fromClause.Expression.GetFirstToken(includeZeroWidth: true);
var endToken = fromClause.Expression.GetLastToken(includeZeroWidth: true);
AddIndentBlockOperation(list, baseToken, startToken, endToken);
}
public override void AddIndentBlockOperations(List<IndentBlockOperation> list, SyntaxNode node, in NextIndentBlockOperationAction nextOperation)
{
nextOperation.Invoke();
if (node is QueryExpressionSyntax queryExpression)
{
AddIndentBlockOperationsForFromClause(list, queryExpression.FromClause);
foreach (var queryClause in queryExpression.Body.Clauses)
{
// if it is nested query expression
if (queryClause is FromClauseSyntax fromClause)
{
AddIndentBlockOperationsForFromClause(list, fromClause);
}
}
// set alignment line for query expression
var baseToken = queryExpression.GetFirstToken(includeZeroWidth: true);
var endToken = queryExpression.GetLastToken(includeZeroWidth: true);
if (!baseToken.IsMissing && !baseToken.Equals(endToken))
{
var startToken = baseToken.GetNextToken(includeZeroWidth: true);
SetAlignmentBlockOperation(list, baseToken, startToken, endToken);
}
}
}
public override void AddAnchorIndentationOperations(List<AnchorIndentationOperation> list, SyntaxNode node, in NextAnchorIndentationOperationAction nextOperation)
{
nextOperation.Invoke();
switch (node)
{
case QueryClauseSyntax queryClause:
{
var firstToken = queryClause.GetFirstToken(includeZeroWidth: true);
AddAnchorIndentationOperation(list, firstToken, queryClause.GetLastToken(includeZeroWidth: true));
return;
}
case SelectOrGroupClauseSyntax selectOrGroupClause:
{
var firstToken = selectOrGroupClause.GetFirstToken(includeZeroWidth: true);
AddAnchorIndentationOperation(list, firstToken, selectOrGroupClause.GetLastToken(includeZeroWidth: true));
return;
}
case QueryContinuationSyntax continuation:
AddAnchorIndentationOperation(list, continuation.IntoKeyword, continuation.GetLastToken(includeZeroWidth: true));
return;
}
}
public override AdjustNewLinesOperation? GetAdjustNewLinesOperation(in SyntaxToken previousToken, in SyntaxToken currentToken, in NextGetAdjustNewLinesOperation nextOperation)
{
if (previousToken.IsNestedQueryExpression())
{
return CreateAdjustNewLinesOperation(0, AdjustNewLinesOption.PreserveLines);
}
// skip the very first from keyword
if (currentToken.IsFirstFromKeywordInExpression())
{
return nextOperation.Invoke(in previousToken, in currentToken);
}
switch (currentToken.Kind())
{
case SyntaxKind.FromKeyword:
case SyntaxKind.WhereKeyword:
case SyntaxKind.LetKeyword:
case SyntaxKind.JoinKeyword:
case SyntaxKind.OrderByKeyword:
case SyntaxKind.GroupKeyword:
case SyntaxKind.SelectKeyword:
if (currentToken.GetAncestor<QueryExpressionSyntax>() != null)
{
if (_options.NewLines.HasFlag(NewLinePlacement.BetweenQueryExpressionClauses))
{
return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines);
}
else
{
return CreateAdjustNewLinesOperation(0, AdjustNewLinesOption.PreserveLines);
}
}
break;
}
return nextOperation.Invoke(in previousToken, in currentToken);
}
}
|