File: Simplification\CSharpSimplificationService.cs
Web Access
Project: src\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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Utilities;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Simplification;
 
[ExportLanguageService(typeof(ISimplificationService), LanguageNames.CSharp), Shared]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed partial class CSharpSimplificationService()
    : AbstractSimplificationService<CompilationUnitSyntax, ExpressionSyntax, StatementSyntax, CrefSyntax>(s_reducers)
{
    // 1. Prefer 'var' simplification first.  In other words we like `var v = (int)x` vs `int v = x`
    // 2. the cast simplifier should run earlier then everything else to minimize the type expressions
    // 3. Extension method reducer may insert parentheses.  So run it before the parentheses remover.
    private static readonly ImmutableArray<AbstractReducer> s_reducers =
        [
            new CSharpVarReducer(),
            new CSharpCastReducer(),
            new CSharpNameReducer(),
            new CSharpNullableAnnotationReducer(),
            new CSharpExtensionMethodReducer(),
            new CSharpParenthesizedExpressionReducer(),
            new CSharpParenthesizedPatternReducer(),
            new CSharpEscapingReducer(),
            new CSharpMiscellaneousReducer(),
            new CSharpInferredMemberNameReducer(),
            new CSharpDefaultExpressionReducer(),
        ];
 
    public override SimplifierOptions DefaultOptions
        => CSharpSimplifierOptions.Default;
 
    public override SimplifierOptions GetSimplifierOptions(IOptionsReader options)
        => new CSharpSimplifierOptions(options);
 
    public override SyntaxNode Expand(SyntaxNode node, SemanticModel semanticModel, SyntaxAnnotation? annotationForReplacedAliasIdentifier, Func<SyntaxNode, bool>? expandInsideNode, bool expandParameter, CancellationToken cancellationToken)
    {
        using (Logger.LogBlock(FunctionId.Simplifier_ExpandNode, cancellationToken))
        {
            if (node is AttributeSyntax or
                AttributeArgumentSyntax or
                ConstructorInitializerSyntax or
                ExpressionSyntax or
                FieldDeclarationSyntax or
                StatementSyntax or
                CrefSyntax or
                XmlNameAttributeSyntax or
                TypeConstraintSyntax or
                BaseTypeSyntax)
            {
                var rewriter = new Expander(semanticModel, expandInsideNode, expandParameter, cancellationToken, annotationForReplacedAliasIdentifier);
                return rewriter.Visit(node);
            }
            else
            {
                throw new ArgumentException(CSharpWorkspaceResources.Only_attributes_constructor_initializers_expressions_or_statements_can_be_made_explicit, nameof(node));
            }
        }
    }
 
    public override SyntaxToken Expand(SyntaxToken token, SemanticModel semanticModel, Func<SyntaxNode, bool>? expandInsideNode, CancellationToken cancellationToken)
    {
        Contract.ThrowIfNull(token.Parent);
 
        using (Logger.LogBlock(FunctionId.Simplifier_ExpandToken, cancellationToken))
        {
            var rewriter = new Expander(semanticModel, expandInsideNode, false, cancellationToken);
 
            var rewrittenToken = TryEscapeIdentifierToken(rewriter.VisitToken(token), token.Parent).WithAdditionalAnnotations(Simplifier.Annotation);
            if (TryAddLeadingElasticTriviaIfNecessary(rewrittenToken, token, out var rewrittenTokenWithElasticTrivia))
            {
                return rewrittenTokenWithElasticTrivia;
            }
 
            return rewrittenToken;
        }
    }
 
    public static SyntaxToken TryEscapeIdentifierToken(SyntaxToken syntaxToken, SyntaxNode parentOfToken)
    {
        // do not escape an already escaped identifier
        if (syntaxToken.IsVerbatimIdentifier())
        {
            return syntaxToken;
        }
 
        if (SyntaxFacts.GetKeywordKind(syntaxToken.ValueText) == SyntaxKind.None && SyntaxFacts.GetContextualKeywordKind(syntaxToken.ValueText) == SyntaxKind.None)
        {
            return syntaxToken;
        }
 
        if (SyntaxFacts.GetContextualKeywordKind(syntaxToken.ValueText) == SyntaxKind.UnderscoreToken)
        {
            return syntaxToken;
        }
 
        var parent = parentOfToken.Parent;
        if (parentOfToken is SimpleNameSyntax && parent.IsKind(SyntaxKind.XmlNameAttribute))
        {
            // do not try to escape XML name attributes
            return syntaxToken;
        }
 
        // do not escape global in a namespace qualified name
        if (parent.IsKind(SyntaxKind.AliasQualifiedName) &&
            syntaxToken.ValueText == "global")
        {
            return syntaxToken;
        }
 
        // safe to escape identifier
        return syntaxToken.CopyAnnotationsTo(
            SyntaxFactory.VerbatimIdentifier(
                syntaxToken.LeadingTrivia,
                syntaxToken.ToString(),
                syntaxToken.ValueText,
                syntaxToken.TrailingTrivia))
                    .WithAdditionalAnnotations(Simplifier.Annotation);
    }
 
    public static T AppendElasticTriviaIfNecessary<T>(T rewrittenNode, T originalNode) where T : SyntaxNode
    {
        var firstRewrittenToken = rewrittenNode.GetFirstToken(true, false, true, true);
        var firstOriginalToken = originalNode.GetFirstToken(true, false, true, true);
        if (TryAddLeadingElasticTriviaIfNecessary(firstRewrittenToken, firstOriginalToken, out var rewrittenTokenWithLeadingElasticTrivia))
        {
            return rewrittenNode.ReplaceToken(firstRewrittenToken, rewrittenTokenWithLeadingElasticTrivia);
        }
 
        return rewrittenNode;
    }
 
    private static bool TryAddLeadingElasticTriviaIfNecessary(SyntaxToken token, SyntaxToken originalToken, out SyntaxToken tokenWithLeadingWhitespace)
    {
        tokenWithLeadingWhitespace = default;
 
        if (token.HasLeadingTrivia)
        {
            return false;
        }
 
        var previousToken = originalToken.GetPreviousToken();
 
        if (previousToken.HasTrailingTrivia)
        {
            return false;
        }
 
        tokenWithLeadingWhitespace = token.WithLeadingTrivia(SyntaxFactory.ElasticMarker).WithAdditionalAnnotations(Formatter.Annotation);
        return true;
    }
 
    protected override SemanticModel GetSpeculativeSemanticModel(ref SyntaxNode nodeToSpeculate, SemanticModel originalSemanticModel, SyntaxNode originalNode)
    {
        var syntaxNodeToSpeculate = nodeToSpeculate;
        Contract.ThrowIfNull(syntaxNodeToSpeculate);
        Contract.ThrowIfFalse(SpeculationAnalyzer.CanSpeculateOnNode(nodeToSpeculate));
        return SpeculationAnalyzer.CreateSpeculativeSemanticModelForNode(originalNode, syntaxNodeToSpeculate, originalSemanticModel);
    }
 
    protected override ImmutableArray<NodeOrTokenToReduce> GetNodesAndTokensToReduce(SyntaxNode root, Func<SyntaxNodeOrToken, bool> isNodeOrTokenOutsideSimplifySpans)
        => NodesAndTokensToReduceComputer.Compute(root, isNodeOrTokenOutsideSimplifySpans);
 
    protected override bool NodeRequiresNonSpeculativeSemanticModel(SyntaxNode node)
        => false;
 
    private const string s_CS8019_UnusedUsingDirective = "CS8019";
 
    protected override void GetUnusedNamespaceImports(SemanticModel model, HashSet<SyntaxNode> namespaceImports, CancellationToken cancellationToken)
    {
        var root = model.SyntaxTree.GetRoot(cancellationToken);
        var diagnostics = model.GetDiagnostics(cancellationToken: cancellationToken);
 
        foreach (var diagnostic in diagnostics)
        {
            if (diagnostic.Id == s_CS8019_UnusedUsingDirective)
            {
                if (root.FindNode(diagnostic.Location.SourceSpan) is UsingDirectiveSyntax node)
                {
                    namespaceImports.Add(node);
                }
            }
        }
    }
 
    // Is the tuple on either side of a deconstruction (top-level or nested)?
    private static bool IsTupleInDeconstruction(SyntaxNode tuple)
    {
        Debug.Assert(tuple.IsKind(SyntaxKind.TupleExpression));
        var currentTuple = tuple;
        do
        {
            var parent = currentTuple.Parent;
            if (parent.IsKind(SyntaxKind.SimpleAssignmentExpression))
            {
                return true;
            }
 
            if (!parent.IsKind(SyntaxKind.Argument))
            {
                return false;
            }
 
            var grandParent = parent.Parent;
            if (!grandParent.IsKind(SyntaxKind.TupleExpression))
            {
                return false;
            }
 
            currentTuple = grandParent;
        }
        while (true);
    }
 
    protected override void AddImportDeclarations(CompilationUnitSyntax root, ArrayBuilder<SyntaxNode> importDeclarations)
    {
        importDeclarations.AddRange(root.Usings);
 
        foreach (var member in root.Members)
        {
            if (member is BaseNamespaceDeclarationSyntax baseNamespace)
                AddImportDeclarations(baseNamespace, importDeclarations);
        }
 
        static void AddImportDeclarations(BaseNamespaceDeclarationSyntax baseNamespace, ArrayBuilder<SyntaxNode> importDeclarations)
        {
            importDeclarations.AddRange(baseNamespace.Usings);
 
            foreach (var member in baseNamespace.Members)
            {
                if (member is BaseNamespaceDeclarationSyntax childNamespace)
                    AddImportDeclarations(childNamespace, importDeclarations);
            }
        }
    }
}