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
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
@@ -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)
Original file line number Diff line number Diff line change
@@ -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(
Original file line number Diff line number Diff line change
@@ -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<SyntaxNode>(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 } &&
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;
}
@@ -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);
Original file line number Diff line number Diff line change
@@ -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)
{
Original file line number Diff line number Diff line change
@@ -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();

Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -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);

Original file line number Diff line number Diff line change
@@ -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)
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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);