| File: src\roslyn\src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\Utilities\TypeStyle\CSharpTypeStyleHelper.cs | Web Access |
| Project: src\roslyn\src\Workspaces\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Workspaces.csproj (Microsoft.CodeAnalysis.CSharp.Workspaces) |
// 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.Diagnostics; using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Simplification; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.CodeAnalysis.CSharp.Utilities; /// <param name="IsStylePreferred"> /// Whether or not converting would transition the code to the style the user prefers. i.e. if the user likes /// <c>var</c> for everything, and you have <c>int i = 0</c> then <see cref="IsStylePreferred"/> will be /// <see langword="true"/>. However, if the user likes <c>var</c> for everything and you have <c>var i = 0</c>, /// then it's still possible to convert that, it would just be <see langword="false"/> for /// <see cref="IsStylePreferred"/> because it goes against the user's preferences. /// <para>In general, most features should only convert the type if <see cref="IsStylePreferred"/> is /// <see langword="true"/>. The one exception is the refactoring, which is explicitly there to still let people /// convert things quickly, even if it's going against their stated style.</para> /// </param> internal readonly record struct TypeStyleResult( bool CanConvert, CSharpTypeStyleHelper.Context Context, bool IsStylePreferred, NotificationOption2 Notification); internal abstract partial class CSharpTypeStyleHelper { protected abstract bool IsStylePreferred(in State state); public virtual TypeStyleResult AnalyzeTypeName( TypeSyntax typeName, SemanticModel semanticModel, CSharpSimplifierOptions options, CancellationToken cancellationToken) { if (typeName?.FirstAncestorOrSelf<SyntaxNode>(a => a.Kind() is SyntaxKind.DeclarationExpression or SyntaxKind.VariableDeclaration or SyntaxKind.ForEachStatement) is not { } declaration) return default; var state = new State( declaration, semanticModel, options, cancellationToken); var isStylePreferred = this.IsStylePreferred(in state); var notificationOption = state.GetDiagnosticSeverityPreference(); var canConvert = this.TryAnalyzeVariableDeclaration( typeName, semanticModel, options, cancellationToken); return new TypeStyleResult( canConvert, state.Context, isStylePreferred, notificationOption); } internal abstract bool TryAnalyzeVariableDeclaration( TypeSyntax typeName, SemanticModel semanticModel, CSharpSimplifierOptions options, CancellationToken cancellationToken); protected abstract bool AssignmentSupportsStylePreference( SyntaxToken identifier, TypeSyntax typeName, ExpressionSyntax initializer, SemanticModel semanticModel, CSharpSimplifierOptions options, CancellationToken cancellationToken); internal TypeSyntax? FindAnalyzableType(SyntaxNode node, SemanticModel semanticModel, CancellationToken cancellationToken) { Debug.Assert(node.Kind() is SyntaxKind.VariableDeclaration or SyntaxKind.ForEachStatement or SyntaxKind.DeclarationExpression); return node switch { VariableDeclarationSyntax variableDeclaration => ShouldAnalyzeVariableDeclaration(variableDeclaration, cancellationToken) ? variableDeclaration.Type : null, ForEachStatementSyntax forEachStatement => ShouldAnalyzeForEachStatement(forEachStatement, semanticModel, cancellationToken) ? forEachStatement.Type : null, DeclarationExpressionSyntax declarationExpression => ShouldAnalyzeDeclarationExpression(declarationExpression, semanticModel, cancellationToken) ? declarationExpression.Type : null, _ => null, }; } public virtual bool ShouldAnalyzeVariableDeclaration(VariableDeclarationSyntax variableDeclaration, CancellationToken cancellationToken) { // implicit type is applicable only for local variables and // such declarations cannot have multiple declarators and // must have an initializer. var isSupportedParentKind = variableDeclaration.Parent is (kind: SyntaxKind.LocalDeclarationStatement or SyntaxKind.ForStatement or SyntaxKind.UsingStatement); return isSupportedParentKind && variableDeclaration.Variables is [{ Initializer: not null }]; } protected virtual bool ShouldAnalyzeForEachStatement(ForEachStatementSyntax forEachStatement, SemanticModel semanticModel, CancellationToken cancellationToken) => true; protected virtual bool ShouldAnalyzeDeclarationExpression(DeclarationExpressionSyntax declaration, SemanticModel semanticModel, CancellationToken cancellationToken) { // Ensure that deconstruction assignment or foreach variable statement have a non-null deconstruct method. DeconstructionInfo? deconstructionInfoOpt = null; switch (declaration.Parent) { case AssignmentExpressionSyntax assignmentExpression: if (assignmentExpression.IsDeconstruction()) { deconstructionInfoOpt = semanticModel.GetDeconstructionInfo(assignmentExpression); } break; case ForEachVariableStatementSyntax forEachVariableStatement: deconstructionInfoOpt = semanticModel.GetDeconstructionInfo(forEachVariableStatement); break; } return !deconstructionInfoOpt.HasValue || !deconstructionInfoOpt.Value.Nested.IsEmpty || deconstructionInfoOpt.Value.Method != null; } }