/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Indentation/CSharpIndentationService.cs
206 строк
9 KB
Cyrus Najmabadi
Simplify code
28 июн 2025, 05:39
28 июн 2025, 05:39
5a9b565
Код
Авторство
О чём код?
// 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 System.Composition; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Formatting; using Microsoft.CodeAnalysis.CSharp.LanguageService; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Indentation; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Indentation; [ExportLanguageService(typeof(IIndentationService), LanguageNames.CSharp), Shared] internal sealed partial class CSharpIndentationService : AbstractIndentationService<CompilationUnitSyntax> { public static readonly CSharpIndentationService Instance = new(); [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Incorrectly used in production code: https://github.com/dotnet/roslyn/issues/42839")] public CSharpIndentationService() { } protected override ISyntaxFacts SyntaxFacts => CSharpSyntaxFacts.Instance; protected override IHeaderFacts HeaderFacts => CSharpHeaderFacts.Instance; protected override ISyntaxFormatting SyntaxFormatting => CSharpSyntaxFormatting.Instance; protected override AbstractFormattingRule GetSpecializedIndentationFormattingRule(FormattingOptions2.IndentStyle indentStyle) => CSharpIndentationFormattingRule.Instance; public static bool ShouldUseSmartTokenFormatterInsteadOfIndenter( IEnumerable<AbstractFormattingRule> formattingRules, CompilationUnitSyntax root, TextLine line, IndentationOptions options, out SyntaxToken token) { Contract.ThrowIfNull(formattingRules); Contract.ThrowIfNull(root); token = default; if (!options.AutoFormattingOptions.FormatOnReturn) { return false; } if (options.IndentStyle != FormattingOptions2.IndentStyle.Smart) { return false; } var firstNonWhitespacePosition = line.GetFirstNonWhitespacePosition(); if (!firstNonWhitespacePosition.HasValue) { return false; } token = root.FindToken(firstNonWhitespacePosition.Value); if (IsInvalidToken(token)) { return false; } if (token.IsKind(SyntaxKind.None) || token.SpanStart != firstNonWhitespacePosition) { return false; } // first see whether there is a line operation for current token var previousToken = token.GetPreviousToken(includeZeroWidth: true); // only use smart token formatter when we have two visible tokens. if (previousToken.Kind() == SyntaxKind.None || previousToken.IsMissing) { return false; } var lineOperation = FormattingOperations.GetAdjustNewLinesOperation(formattingRules, previousToken, token, options.FormattingOptions); if (lineOperation == null || lineOperation.Option == AdjustNewLinesOption.ForceLinesIfOnSingleLine) { // no indentation operation, nothing to do for smart token formatter return false; } // We're pressing enter between two tokens, have the formatter figure out hte appropriate // indentation. return true; } private static bool IsInvalidToken(SyntaxToken token) { // invalid token to be formatted return token.Kind() is SyntaxKind.None or SyntaxKind.EndOfDirectiveToken or SyntaxKind.EndOfFileToken; } private sealed class CSharpIndentationFormattingRule : AbstractFormattingRule { public static readonly AbstractFormattingRule Instance = new CSharpIndentationFormattingRule(); public override void AddIndentBlockOperations(List<IndentBlockOperation> list, SyntaxNode node, in NextIndentBlockOperationAction nextOperation) { nextOperation.Invoke(); ReplaceCaseIndentationRules(list, node); if (node is BaseParameterListSyntax || node is TypeArgumentListSyntax || node is TypeParameterListSyntax || node.IsKind(SyntaxKind.Interpolation)) { AddIndentBlockOperations(list, node); return; } if (node is BaseArgumentListSyntax argument && !argument.Parent.IsKind(SyntaxKind.ThisConstructorInitializer) && !IsBracketedArgumentListMissingBrackets(argument as BracketedArgumentListSyntax)) { AddIndentBlockOperations(list, argument); return; } // only valid if the user has started to actually type a constructor initializer if (node is ConstructorInitializerSyntax constructorInitializer && constructorInitializer.ArgumentList.OpenParenToken.Kind() != SyntaxKind.None && !constructorInitializer.ThisOrBaseKeyword.IsMissing) { var text = node.SyntaxTree.GetText(); // 3 different cases // first case : this or base is the first token on line // second case : colon is the first token on line var colonIsFirstTokenOnLine = !constructorInitializer.ColonToken.IsMissing && constructorInitializer.ColonToken.IsFirstTokenOnLine(text); var thisOrBaseIsFirstTokenOnLine = !constructorInitializer.ThisOrBaseKeyword.IsMissing && constructorInitializer.ThisOrBaseKeyword.IsFirstTokenOnLine(text); if (colonIsFirstTokenOnLine || thisOrBaseIsFirstTokenOnLine) { list.Add(FormattingOperations.CreateRelativeIndentBlockOperation( constructorInitializer.ThisOrBaseKeyword, constructorInitializer.ArgumentList.OpenParenToken.GetNextToken(includeZeroWidth: true), constructorInitializer.ArgumentList.CloseParenToken.GetPreviousToken(includeZeroWidth: true), indentationDelta: 1, option: IndentBlockOption.RelativePosition)); } else { // third case : none of them are the first token on the line AddIndentBlockOperations(list, constructorInitializer.ArgumentList); } } } private static bool IsBracketedArgumentListMissingBrackets(BracketedArgumentListSyntax? node) => node != null && node.OpenBracketToken.IsMissing && node.CloseBracketToken.IsMissing; private static void ReplaceCaseIndentationRules(List<IndentBlockOperation> list, SyntaxNode node) { if (node is not SwitchSectionSyntax { Statements: [var firstStatement, ..] and [.., var lastStatement] }) return; var startToken = firstStatement.GetFirstToken(includeZeroWidth: true); var endToken = lastStatement.GetLastToken(includeZeroWidth: true); for (var i = 0; i < list.Count; i++) { var operation = list[i]; if (operation.StartToken == startToken && operation.EndToken == endToken) { // replace operation list[i] = FormattingOperations.CreateIndentBlockOperation(startToken, endToken, indentationDelta: 1, option: IndentBlockOption.RelativePosition); } } } private static void AddIndentBlockOperations(List<IndentBlockOperation> list, SyntaxNode node) { RoslynDebug.AssertNotNull(node.Parent); // only add indent block operation if the base token is the first token on line var baseToken = node.Parent.GetFirstToken(includeZeroWidth: true); list.Add(FormattingOperations.CreateRelativeIndentBlockOperation( baseToken, node.GetFirstToken(includeZeroWidth: true).GetNextToken(includeZeroWidth: true), node.GetLastToken(includeZeroWidth: true).GetPreviousToken(includeZeroWidth: true), indentationDelta: 1, option: IndentBlockOption.RelativeToFirstTokenOnBaseTokenLine)); } } }