diff --git a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs index dcfcda77df3ad..9b3b9c3b4c8e5 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SemanticClassifierTests.cs @@ -3559,6 +3559,35 @@ void Goo() EnumMember("IgnorePatternWhitespace")); } + [Theory] + [CombinatorialData] + public async Task TestRegexOnApiWithStringSyntaxAttribute_Attribute(TestHost testHost) + { + await TestAsync( +@" +using System; +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; + +[AttributeUsage(AttributeTargets.Field)] +class RegexTestAttribute : Attribute +{ + public RegexTestAttribute([StringSyntax(StringSyntaxAttribute.Regex)] string value) { } +} + +class Program +{ + [|[RegexTest(@""$\a(?#comment)"")]|] + private string field; +}" + EmbeddedLanguagesTestConstants.StringSyntaxAttributeCodeCSharp, +testHost, +Class("RegexTest"), +Regex.Anchor("$"), +Regex.OtherEscape("\\"), +Regex.OtherEscape("a"), +Regex.Comment("(?#comment)")); + } + [Theory] [CombinatorialData] public async Task TestIncompleteRegexLeadingToStringInsideSkippedTokensInsideADirective(TestHost testHost) diff --git a/src/EditorFeatures/VisualBasicTest/Classification/SemanticClassifierTests.vb b/src/EditorFeatures/VisualBasicTest/Classification/SemanticClassifierTests.vb index 201ea6b3f834d..addc3258329f2 100644 --- a/src/EditorFeatures/VisualBasicTest/Classification/SemanticClassifierTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Classification/SemanticClassifierTests.vb @@ -654,6 +654,39 @@ Regex.Anchor("z"), Regex.Grouping(")")) End Function + + Public Async Function TestRegexStringSyntaxAttribute_Attribute(testHost As TestHost) As Task + Await TestAsync( +" +imports system +imports System.Diagnostics.CodeAnalysis +imports System.Text.RegularExpressions + + +class RegexTestAttribute + inherits Attribute + + public sub new( value as string) + end sub +end class + +class Program + [||] + dim field as string +end class" & EmbeddedLanguagesTestConstants.StringSyntaxAttributeCodeVB, + testHost, +[Class]("RegexTest"), +Regex.Anchor("$"), +Regex.Grouping("("), +Regex.Anchor("\"), +Regex.Anchor("b"), +Regex.Anchor("\"), +Regex.Anchor("G"), +Regex.Anchor("\"), +Regex.Anchor("z"), +Regex.Grouping(")")) + End Function + Public Async Function TestRegexStringSyntaxAttribute_Property(testHost As TestHost) As Task Await TestAsync( diff --git a/src/Features/Core/Portable/EmbeddedLanguages/AbstractLanguageDetector.cs b/src/Features/Core/Portable/EmbeddedLanguages/AbstractLanguageDetector.cs index 720e82edaf7ae..7c6c2b32922d3 100644 --- a/src/Features/Core/Portable/EmbeddedLanguages/AbstractLanguageDetector.cs +++ b/src/Features/Core/Portable/EmbeddedLanguages/AbstractLanguageDetector.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using System.Threading; using Microsoft.CodeAnalysis.EmbeddedLanguages.LanguageServices; @@ -150,6 +151,12 @@ private bool IsEmbeddedLanguageStringLiteralToken(SyntaxToken token, SemanticMod if (IsArgumentToParameterWithMatchingStringSyntaxAttribute(semanticModel, argument, cancellationToken, out options)) return true; } + else if (syntaxFacts.IsAttributeArgument(parent.Parent)) + { + var argument = parent.Parent; + if (IsArgumentToAttributeParameterWithMatchingStringSyntaxAttribute(semanticModel, argument, cancellationToken, out options)) + return true; + } else { var statement = parent.FirstAncestorOrSelf(syntaxFacts.IsStatement); @@ -167,13 +174,24 @@ private bool IsEmbeddedLanguageStringLiteralToken(SyntaxToken token, SemanticMod return false; } - private bool IsArgumentToParameterWithMatchingStringSyntaxAttribute(SemanticModel semanticModel, SyntaxNode argumentNode, CancellationToken cancellationToken, out TOptions options) + private bool IsArgumentToAttributeParameterWithMatchingStringSyntaxAttribute( + SemanticModel semanticModel, SyntaxNode argument, CancellationToken cancellationToken, out TOptions options) + { + var parameter = Info.SemanticFacts.FindParameterForAttributeArgument(semanticModel, argument, cancellationToken); + return IsParameterWithMatchingStringSyntaxAttribute(semanticModel, argument, parameter, cancellationToken, out options); + } + + private bool IsArgumentToParameterWithMatchingStringSyntaxAttribute(SemanticModel semanticModel, SyntaxNode argument, CancellationToken cancellationToken, out TOptions options) { - var operation = semanticModel.GetOperation(argumentNode, cancellationToken); - if (operation is IArgumentOperation { Parameter: { } parameter } && - HasMatchingStringSyntaxAttribute(parameter)) + var parameter = Info.SemanticFacts.FindParameterForArgument(semanticModel, argument, cancellationToken); + return IsParameterWithMatchingStringSyntaxAttribute(semanticModel, argument, parameter, cancellationToken, out options); + } + + private bool IsParameterWithMatchingStringSyntaxAttribute(SemanticModel semanticModel, SyntaxNode argument, IParameterSymbol parameter, CancellationToken cancellationToken, out TOptions options) + { + if (HasMatchingStringSyntaxAttribute(parameter)) { - options = GetOptionsFromSiblingArgument(argumentNode, semanticModel, cancellationToken) ?? + options = GetOptionsFromSiblingArgument(argument, semanticModel, cancellationToken) ?? GetStringSyntaxDefaultOptions(); return true; } @@ -190,12 +208,15 @@ private bool IsFieldOrPropertyWithMatchingStringSyntaxAttribute( HasMatchingStringSyntaxAttribute(symbol); } - private bool HasMatchingStringSyntaxAttribute(ISymbol symbol) + private bool HasMatchingStringSyntaxAttribute([NotNullWhen(true)] ISymbol? symbol) { - foreach (var attribute in symbol.GetAttributes()) + if (symbol != null) { - if (IsMatchingStringSyntaxAttribute(attribute)) - return true; + foreach (var attribute in symbol.GetAttributes()) + { + if (IsMatchingStringSyntaxAttribute(attribute)) + return true; + } } return false; @@ -241,18 +262,22 @@ private bool IsMatchingStringSyntaxAttribute(AttributeData attribute) } protected TOptions? GetOptionsFromSiblingArgument( - SyntaxNode argumentNode, + SyntaxNode argument, SemanticModel semanticModel, CancellationToken cancellationToken) { var syntaxFacts = Info.SyntaxFacts; - var argumentList = argumentNode.GetRequiredParent(); - var arguments = syntaxFacts.GetArgumentsOfArgumentList(argumentList); + var argumentList = argument.GetRequiredParent(); + var arguments = syntaxFacts.IsArgument(argument) + ? syntaxFacts.GetArgumentsOfArgumentList(argumentList) + : syntaxFacts.GetArgumentsOfAttributeArgumentList(argumentList); foreach (var siblingArg in arguments) { - if (siblingArg != argumentNode) + if (siblingArg != argument) { - var expr = syntaxFacts.GetExpressionOfArgument(siblingArg); + var expr = syntaxFacts.IsArgument(argument) + ? syntaxFacts.GetExpressionOfArgument(siblingArg) + : syntaxFacts.GetExpressionOfAttributeArgument(siblingArg); if (expr != null) { var exprType = semanticModel.GetTypeInfo(expr, cancellationToken); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs index 92e4b09c8f89a..7c30407135668 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs @@ -269,8 +269,11 @@ public IEnumerable GetDeclaredSymbols( } } - public IParameterSymbol FindParameterForArgument(SemanticModel semanticModel, SyntaxNode argumentNode, CancellationToken cancellationToken) - => ((ArgumentSyntax)argumentNode).DetermineParameter(semanticModel, allowParams: false, cancellationToken); + public IParameterSymbol FindParameterForArgument(SemanticModel semanticModel, SyntaxNode argument, CancellationToken cancellationToken) + => ((ArgumentSyntax)argument).DetermineParameter(semanticModel, allowParams: false, cancellationToken); + + public IParameterSymbol FindParameterForAttributeArgument(SemanticModel semanticModel, SyntaxNode argument, CancellationToken cancellationToken) + => ((AttributeArgumentSyntax)argument).DetermineParameter(semanticModel, allowParams: false, cancellationToken); public ImmutableArray GetBestOrAllSymbols(SemanticModel semanticModel, SyntaxNode node, SyntaxToken token, CancellationToken cancellationToken) { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index 7488baa25ee76..bb0272c6130ef 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -1183,6 +1183,9 @@ public SeparatedSyntaxList GetArgumentsOfObjectCreationExpression(Sy public SeparatedSyntaxList GetArgumentsOfArgumentList(SyntaxNode argumentList) => ((BaseArgumentListSyntax)argumentList).Arguments; + public SeparatedSyntaxList GetArgumentsOfAttributeArgumentList(SyntaxNode argumentList) + => ((AttributeArgumentListSyntax)argumentList).Arguments; + public bool IsRegularComment(SyntaxTrivia trivia) => trivia.IsRegularComment(); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SemanticFacts/ISemanticFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SemanticFacts/ISemanticFacts.cs index 7c909824afe4b..6da22448b8798 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SemanticFacts/ISemanticFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SemanticFacts/ISemanticFacts.cs @@ -89,7 +89,8 @@ internal partial interface ISemanticFacts IEnumerable GetDeclaredSymbols(SemanticModel semanticModel, SyntaxNode memberDeclaration, CancellationToken cancellationToken); - IParameterSymbol FindParameterForArgument(SemanticModel semanticModel, SyntaxNode argumentNode, CancellationToken cancellationToken); + IParameterSymbol FindParameterForArgument(SemanticModel semanticModel, SyntaxNode argument, CancellationToken cancellationToken); + IParameterSymbol FindParameterForAttributeArgument(SemanticModel semanticModel, SyntaxNode argument, CancellationToken cancellationToken); #nullable enable ImmutableArray GetBestOrAllSymbols(SemanticModel semanticModel, SyntaxNode? node, SyntaxToken token, CancellationToken cancellationToken); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs index db9fc86fcb0a0..d3359f7ae7ec1 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs @@ -324,6 +324,7 @@ void GetPartsOfInterpolationExpression(SyntaxNode node, SeparatedSyntaxList GetArgumentsOfInvocationExpression(SyntaxNode node); SeparatedSyntaxList GetArgumentsOfObjectCreationExpression(SyntaxNode node); SeparatedSyntaxList GetArgumentsOfArgumentList(SyntaxNode node); + SeparatedSyntaxList GetArgumentsOfAttributeArgumentList(SyntaxNode node); bool IsUsingDirectiveName([NotNullWhen(true)] SyntaxNode? node); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SemanticFacts/VisualBasicSemanticFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SemanticFacts/VisualBasicSemanticFacts.vb index 3888b58500b56..ded95e302ab56 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SemanticFacts/VisualBasicSemanticFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SemanticFacts/VisualBasicSemanticFacts.vb @@ -99,11 +99,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return False End Function - Public ReadOnly Property SupportsParameterizedProperties As Boolean Implements ISemanticFacts.SupportsParameterizedProperties - Get - Return True - End Get - End Property + Public ReadOnly Property SupportsParameterizedProperties As Boolean = True Implements ISemanticFacts.SupportsParameterizedProperties Public Function TryGetSpeculativeSemanticModel(oldSemanticModel As SemanticModel, oldNode As SyntaxNode, newNode As SyntaxNode, ByRef speculativeModel As SemanticModel) As Boolean Implements ISemanticFacts.TryGetSpeculativeSemanticModel Debug.Assert(oldNode.Kind = newNode.Kind) @@ -230,8 +226,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return SpecializedCollections.SingletonEnumerable(semanticModel.GetDeclaredSymbol(memberDeclaration, cancellationToken)) End Function - Public Function FindParameterForArgument(semanticModel As SemanticModel, argumentNode As SyntaxNode, cancellationToken As CancellationToken) As IParameterSymbol Implements ISemanticFacts.FindParameterForArgument - Return DirectCast(argumentNode, ArgumentSyntax).DetermineParameter(semanticModel, allowParamArray:=False, cancellationToken) + Public Function FindParameterForArgument(semanticModel As SemanticModel, argument As SyntaxNode, cancellationToken As CancellationToken) As IParameterSymbol Implements ISemanticFacts.FindParameterForArgument + Return DirectCast(argument, ArgumentSyntax).DetermineParameter(semanticModel, allowParamArray:=False, cancellationToken) + End Function + + Public Function FindParameterForAttributeArgument(semanticModel As SemanticModel, argument As SyntaxNode, cancellationToken As CancellationToken) As IParameterSymbol Implements ISemanticFacts.FindParameterForAttributeArgument + Return FindParameterForArgument(semanticModel, argument, cancellationToken) End Function Public Function GetBestOrAllSymbols(semanticModel As SemanticModel, node As SyntaxNode, token As SyntaxToken, cancellationToken As CancellationToken) As ImmutableArray(Of ISymbol) Implements ISemanticFacts.GetBestOrAllSymbols diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb index c238dc88e4510..ea168a1bea88d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb @@ -1222,6 +1222,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices Return DirectCast(node, ArgumentListSyntax).Arguments End Function + Public Function GetArgumentsOfAttributeArgumentList(node As SyntaxNode) As SeparatedSyntaxList(Of SyntaxNode) Implements ISyntaxFacts.GetArgumentsOfAttributeArgumentList + Return GetArgumentsOfArgumentList(node) + End Function + Public Function ConvertToSingleLine(node As SyntaxNode, Optional useElasticTrivia As Boolean = False) As SyntaxNode Implements ISyntaxFacts.ConvertToSingleLine Return node.ConvertToSingleLine(useElasticTrivia) End Function diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs index 9b098924f97f5..7f02fc0730b34 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs @@ -157,6 +157,9 @@ public IEnumerable GetDeclaredSymbols(SemanticModel semanticModel, Synt public IParameterSymbol FindParameterForArgument(SemanticModel semanticModel, SyntaxNode argumentNode, CancellationToken cancellationToken) => SemanticFacts.FindParameterForArgument(semanticModel, argumentNode, cancellationToken); + public IParameterSymbol FindParameterForAttributeArgument(SemanticModel semanticModel, SyntaxNode argumentNode, CancellationToken cancellationToken) + => SemanticFacts.FindParameterForAttributeArgument(semanticModel, argumentNode, cancellationToken); + public ImmutableArray GetBestOrAllSymbols(SemanticModel semanticModel, SyntaxNode node, SyntaxToken token, CancellationToken cancellationToken) => SemanticFacts.GetBestOrAllSymbols(semanticModel, node, token, cancellationToken);