Skip to content

Commit

Permalink
Support the StringSyntax attribute actually being used on an attribut…
Browse files Browse the repository at this point in the history
…e parameter.
  • Loading branch information
CyrusNajmabadi committed Feb 7, 2022
1 parent 4224320 commit 1e611a2
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 25 deletions.
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 } &&
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 @@ -4,7 +4,7 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<RootNamespace>Microsoft.CodeAnalysis.Remote</RootNamespace>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net6.0-windows;netstandard2.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- NuGet -->
<IsPackable>true</IsPackable>
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

0 comments on commit 1e611a2

Please sign in to comment.