Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support the StringSyntax attribute actually being used on an attribute parameter. #59343

Merged
merged 2 commits into from
Feb 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,39 @@ Regex.Anchor("z"),
Regex.Grouping(")"))
End Function

<WpfTheory, CombinatorialData>
Public Async Function TestRegexStringSyntaxAttribute_Attribute(testHost As TestHost) As Task
Await TestAsync(
"
imports system
imports System.Diagnostics.CodeAnalysis
imports System.Text.RegularExpressions

<AttributeUsage(AttributeTargets.Field)>
class RegexTestAttribute
inherits Attribute

public sub new(<StringSyntax(StringSyntaxAttribute.Regex)> value as string)
end sub
end class

class Program
[|<RegexTest(""$(\b\G\z)"")>|]
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

<WpfTheory, CombinatorialData>
Public Async Function TestRegexStringSyntaxAttribute_Property(testHost As TestHost) As Task
Await TestAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<SyntaxNode>(syntaxFacts.IsStatement);
Expand All @@ -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 } &&
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

had to mvoe off of IOp. it doesn't work for attribute arguments unfortunately.

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;
}
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,11 @@ public IEnumerable<ISymbol> 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<ISymbol> GetBestOrAllSymbols(SemanticModel semanticModel, SyntaxNode node, SyntaxToken token, CancellationToken cancellationToken)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1183,6 +1183,9 @@ public SeparatedSyntaxList<SyntaxNode> GetArgumentsOfObjectCreationExpression(Sy
public SeparatedSyntaxList<SyntaxNode> GetArgumentsOfArgumentList(SyntaxNode argumentList)
=> ((BaseArgumentListSyntax)argumentList).Arguments;

public SeparatedSyntaxList<SyntaxNode> GetArgumentsOfAttributeArgumentList(SyntaxNode argumentList)
=> ((AttributeArgumentListSyntax)argumentList).Arguments;

public bool IsRegularComment(SyntaxTrivia trivia)
=> trivia.IsRegularComment();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ internal partial interface ISemanticFacts

IEnumerable<ISymbol> 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<ISymbol> GetBestOrAllSymbols(SemanticModel semanticModel, SyntaxNode? node, SyntaxToken token, CancellationToken cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ void GetPartsOfInterpolationExpression(SyntaxNode node,
SeparatedSyntaxList<SyntaxNode> GetArgumentsOfInvocationExpression(SyntaxNode node);
SeparatedSyntaxList<SyntaxNode> GetArgumentsOfObjectCreationExpression(SyntaxNode node);
SeparatedSyntaxList<SyntaxNode> GetArgumentsOfArgumentList(SyntaxNode node);
SeparatedSyntaxList<SyntaxNode> GetArgumentsOfAttributeArgumentList(SyntaxNode node);

bool IsUsingDirectiveName([NotNullWhen(true)] SyntaxNode? node);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, <Out> ByRef speculativeModel As SemanticModel) As Boolean Implements ISemanticFacts.TryGetSpeculativeSemanticModel
Debug.Assert(oldNode.Kind = newNode.Kind)
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ public IEnumerable<ISymbol> 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<ISymbol> GetBestOrAllSymbols(SemanticModel semanticModel, SyntaxNode node, SyntaxToken token, CancellationToken cancellationToken)
=> SemanticFacts.GetBestOrAllSymbols(semanticModel, node, token, cancellationToken);

Expand Down