| File: Simplification\Reducers\CSharpEscapingReducer.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. #nullable disable using System; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Utilities; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CSharp.Simplification; internal sealed partial class CSharpEscapingReducer : AbstractCSharpReducer { private static readonly ObjectPool<IReductionRewriter> s_pool = new( () => new Rewriter(s_pool)); private static readonly Func<SyntaxToken, SemanticModel, CSharpSimplifierOptions, CancellationToken, SyntaxToken> s_simplifyIdentifierToken = SimplifyIdentifierToken; public CSharpEscapingReducer() : base(s_pool) { } protected override bool IsApplicable(CSharpSimplifierOptions options) => true; private static SyntaxToken SimplifyIdentifierToken( SyntaxToken token, SemanticModel semanticModel, CSharpSimplifierOptions options, CancellationToken cancellationToken) { var unescapedIdentifier = token.ValueText; var enclosingXmlNameAttr = token.GetAncestor<XmlNameAttributeSyntax>(); // always escape keywords if (SyntaxFacts.GetKeywordKind(unescapedIdentifier) != SyntaxKind.None && enclosingXmlNameAttr == null) { return CreateNewIdentifierTokenFromToken(token, escape: true); } // Escape the Await Identifier if within the Single Line Lambda & Multi Line Context // and async method var parent = token.Parent; if (SyntaxFacts.GetContextualKeywordKind(unescapedIdentifier) == SyntaxKind.AwaitKeyword) { var enclosingLambdaExpression = parent.GetAncestorOrThis<LambdaExpressionSyntax>(); if (enclosingLambdaExpression != null && enclosingLambdaExpression.AsyncKeyword != default) return token; var enclosingMethodBlock = parent.GetAncestorOrThis<MethodDeclarationSyntax>(); if (enclosingMethodBlock != null && enclosingMethodBlock.Modifiers.Any(SyntaxKind.AsyncKeyword)) return token; } // within a query all contextual query keywords need to be escaped, even if they appear in a non query context. if (token.GetAncestors(n => n is QueryExpressionSyntax).Any()) { switch (SyntaxFacts.GetContextualKeywordKind(unescapedIdentifier)) { case SyntaxKind.FromKeyword: case SyntaxKind.WhereKeyword: case SyntaxKind.SelectKeyword: case SyntaxKind.GroupKeyword: case SyntaxKind.IntoKeyword: case SyntaxKind.OrderByKeyword: case SyntaxKind.JoinKeyword: case SyntaxKind.LetKeyword: case SyntaxKind.InKeyword: case SyntaxKind.OnKeyword: case SyntaxKind.EqualsKeyword: case SyntaxKind.ByKeyword: case SyntaxKind.AscendingKeyword: case SyntaxKind.DescendingKeyword: return CreateNewIdentifierTokenFromToken(token, escape: true); } } var result = token.Kind() == SyntaxKind.IdentifierToken ? CreateNewIdentifierTokenFromToken(token, escape: false) : token; // we can't remove the escaping if this would change the semantic. This can happen in cases // where there are two attribute declarations: one with and one without the attribute // suffix. if (SyntaxFacts.IsAttributeName(parent)) { var expression = (SimpleNameSyntax)parent; var newExpression = expression.WithIdentifier(result); var speculationAnalyzer = new SpeculationAnalyzer(expression, newExpression, semanticModel, cancellationToken); if (speculationAnalyzer.ReplacementChangesSemantics()) { return CreateNewIdentifierTokenFromToken(token, escape: true); } } // TODO: handle crefs and param names of xml doc comments. // crefs have the same escaping rules than csharp, param names do not allow escaping in Dev11, but // we may want to change that for Roslyn (Bug 17984, " Could treat '@' specially in <param>, <typeparam>, etc") return result; } private static SyntaxToken CreateNewIdentifierTokenFromToken(SyntaxToken originalToken, bool escape) { var isVerbatimIdentifier = originalToken.IsVerbatimIdentifier(); if (isVerbatimIdentifier == escape) { return originalToken; } var unescapedText = isVerbatimIdentifier ? originalToken.ToString()[1..] : originalToken.ToString(); return escape ? originalToken.CopyAnnotationsTo(SyntaxFactory.VerbatimIdentifier(originalToken.LeadingTrivia, unescapedText, originalToken.ValueText, originalToken.TrailingTrivia)) : originalToken.CopyAnnotationsTo(SyntaxFactory.Identifier(originalToken.LeadingTrivia, SyntaxKind.IdentifierToken, unescapedText, originalToken.ValueText, originalToken.TrailingTrivia)); } }