Skip to content

Commit

Permalink
Implement light-up support for SeparatedSyntaxList<T>
Browse files Browse the repository at this point in the history
  • Loading branch information
sharwell committed May 30, 2017
1 parent afb97e4 commit ffc775f
Show file tree
Hide file tree
Showing 7 changed files with 615 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace StyleCop.Analyzers.Test.CSharp7.Lightup
using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using StyleCop.Analyzers.Helpers;
using StyleCop.Analyzers.Lightup;
using Xunit;
Expand All @@ -19,7 +20,11 @@ public void TestNull()
var parenthesizedVariableDesignationSyntax = (ParenthesizedVariableDesignationSyntaxWrapper)syntaxNode;
Assert.Null(parenthesizedVariableDesignationSyntax.SyntaxNode);
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.OpenParenToken);
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.Variables);
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.CloseParenToken);
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.WithOpenParenToken(SyntaxFactory.Token(SyntaxKind.OpenParenToken)));
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.WithVariables(new SeparatedSyntaxListWrapper<VariableDesignationSyntaxWrapper>.AutoWrapSeparatedSyntaxList<VariableDesignationSyntax>(SyntaxFactory.SeparatedList<VariableDesignationSyntax>())));
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.WithCloseParenToken(SyntaxFactory.Token(SyntaxKind.CloseParenToken)));
}

[Fact]
Expand All @@ -39,6 +44,12 @@ public void TestProperties()
Assert.False(syntaxNode.OpenParenToken.IsEquivalentTo(parenthesizedVariableDesignationSyntax.OpenParenToken));
Assert.True(syntaxNode.CloseParenToken.IsEquivalentTo(parenthesizedVariableDesignationSyntax.CloseParenToken));

var variables = SyntaxFactory.SingletonSeparatedList<VariableDesignationSyntax>(SyntaxFactory.DiscardDesignation());
parenthesizedVariableDesignationSyntax = parenthesizedVariableDesignationSyntax.WithVariables(new SeparatedSyntaxListWrapper<VariableDesignationSyntaxWrapper>.AutoWrapSeparatedSyntaxList<VariableDesignationSyntax>(variables));
Assert.Same(
((ParenthesizedVariableDesignationSyntax)parenthesizedVariableDesignationSyntax.SyntaxNode).Variables[0],
parenthesizedVariableDesignationSyntax.Variables[0].SyntaxNode);

parenthesizedVariableDesignationSyntax = parenthesizedVariableDesignationSyntax.WithCloseParenToken(SpacingExtensions.WithoutTrivia(SyntaxFactory.Token(SyntaxKind.CloseParenToken)));
Assert.NotNull(parenthesizedVariableDesignationSyntax.SyntaxNode);
Assert.False(syntaxNode.OpenParenToken.IsEquivalentTo(parenthesizedVariableDesignationSyntax.OpenParenToken));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ public void TestNull()
var parenthesizedVariableDesignationSyntax = (ParenthesizedVariableDesignationSyntaxWrapper)syntaxNode;
Assert.Null(parenthesizedVariableDesignationSyntax.SyntaxNode);
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.OpenParenToken);
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.Variables);
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.CloseParenToken);
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.WithOpenParenToken(SyntaxFactory.Token(SyntaxKind.OpenParenToken)));
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.WithVariables(new SeparatedSyntaxListWrapper<VariableDesignationSyntaxWrapper>.UnsupportedSyntaxList()));
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.WithCloseParenToken(SyntaxFactory.Token(SyntaxKind.CloseParenToken)));
}

[Fact]
Expand Down
122 changes: 122 additions & 0 deletions StyleCop.Analyzers/StyleCop.Analyzers/Lightup/LightupHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,62 @@ internal static Func<TSyntax, TProperty> CreateSyntaxPropertyAccessor<TSyntax, T
return expression.Compile();
}

internal static Func<TSyntax, SeparatedSyntaxListWrapper<TProperty>> CreateSeparatedSyntaxListPropertyAccessor<TSyntax, TProperty>(Type type, string propertyName)
{
Func<TSyntax, SeparatedSyntaxListWrapper<TProperty>> fallbackAccessor =
syntax =>
{
if (syntax == null)
{
// Unlike an extension method which would throw ArgumentNullException here, the light-up
// behavior needs to match behavior of the underlying property.
throw new NullReferenceException();
}
return SeparatedSyntaxListWrapper<TProperty>.UnsupportedEmpty;
};

if (type == null)
{
return fallbackAccessor;
}

if (!typeof(TSyntax).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
{
throw new InvalidOperationException();
}

var property = type.GetTypeInfo().GetDeclaredProperty(propertyName);
if (property == null)
{
return fallbackAccessor;
}

if (property.PropertyType.GetGenericTypeDefinition() != typeof(SeparatedSyntaxList<>))
{
throw new InvalidOperationException();
}

var propertySyntaxType = property.PropertyType.GenericTypeArguments[0];

var syntaxParameter = Expression.Parameter(typeof(TSyntax), "syntax");
Expression instance =
type.GetTypeInfo().IsAssignableFrom(typeof(TSyntax).GetTypeInfo())
? (Expression)syntaxParameter
: Expression.Convert(syntaxParameter, type);
Expression propertyAccess = Expression.Call(instance, property.GetMethod);

var unboundWrapperType = typeof(SeparatedSyntaxListWrapper<>.AutoWrapSeparatedSyntaxList<>);
var boundWrapperType = unboundWrapperType.MakeGenericType(typeof(TProperty), propertySyntaxType);
var constructorInfo = boundWrapperType.GetTypeInfo().DeclaredConstructors.Single();

Expression<Func<TSyntax, SeparatedSyntaxListWrapper<TProperty>>> expression =
Expression.Lambda<Func<TSyntax, SeparatedSyntaxListWrapper<TProperty>>>(
Expression.New(constructorInfo, propertyAccess),
syntaxParameter);
return expression.Compile();
}

internal static Func<TSyntax, TProperty, TSyntax> CreateSyntaxWithPropertyAccessor<TSyntax, TProperty>(Type type, string propertyName)
{
Func<TSyntax, TProperty, TSyntax> fallbackAccessor =
Expand Down Expand Up @@ -158,5 +214,71 @@ internal static Func<TSyntax, TProperty, TSyntax> CreateSyntaxWithPropertyAccess
valueParameter);
return expression.Compile();
}

internal static Func<TSyntax, SeparatedSyntaxListWrapper<TProperty>, TSyntax> CreateSeparatedSyntaxListWithPropertyAccessor<TSyntax, TProperty>(Type type, string propertyName)
{
Func<TSyntax, SeparatedSyntaxListWrapper<TProperty>, TSyntax> fallbackAccessor =
(syntax, newValue) =>
{
if (syntax == null)
{
// Unlike an extension method which would throw ArgumentNullException here, the light-up
// behavior needs to match behavior of the underlying property.
throw new NullReferenceException();
}
if (ReferenceEquals(newValue, null))
{
return syntax;
}
throw new NotSupportedException();
};

if (type == null)
{
return fallbackAccessor;
}

if (!typeof(TSyntax).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
{
throw new InvalidOperationException();
}

var property = type.GetTypeInfo().GetDeclaredProperty(propertyName);
if (property == null)
{
return fallbackAccessor;
}

if (property.PropertyType.GetGenericTypeDefinition() != typeof(SeparatedSyntaxList<>))
{
throw new InvalidOperationException();
}

var propertySyntaxType = property.PropertyType.GenericTypeArguments[0];

var methodInfo = type.GetTypeInfo().GetDeclaredMethods("With" + propertyName)
.Single(m => !m.IsStatic && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.Equals(property.PropertyType));

var syntaxParameter = Expression.Parameter(typeof(TSyntax), "syntax");
var valueParameter = Expression.Parameter(typeof(SeparatedSyntaxListWrapper<TProperty>), methodInfo.GetParameters()[0].Name);
Expression instance =
type.GetTypeInfo().IsAssignableFrom(typeof(TSyntax).GetTypeInfo())
? (Expression)syntaxParameter
: Expression.Convert(syntaxParameter, type);

var underlyingListProperty = typeof(SeparatedSyntaxListWrapper<TProperty>).GetTypeInfo().GetDeclaredProperty(nameof(SeparatedSyntaxListWrapper<TProperty>.UnderlyingList));
Expression value = Expression.Convert(
Expression.Call(valueParameter, underlyingListProperty.GetMethod),
property.PropertyType);

Expression<Func<TSyntax, SeparatedSyntaxListWrapper<TProperty>, TSyntax>> expression =
Expression.Lambda<Func<TSyntax, SeparatedSyntaxListWrapper<TProperty>, TSyntax>>(
Expression.Call(instance, methodInfo, value),
syntaxParameter,
valueParameter);
return expression.Compile();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ internal struct ParenthesizedVariableDesignationSyntaxWrapper : ISyntaxWrapper<C
private static readonly Type ParenthesizedVariableDesignationSyntaxType;

private static readonly Func<CSharpSyntaxNode, SyntaxToken> OpenParenTokenAccessor;
private static readonly Func<CSharpSyntaxNode, SeparatedSyntaxListWrapper<VariableDesignationSyntaxWrapper>> VariablesAccessor;
private static readonly Func<CSharpSyntaxNode, SyntaxToken> CloseParenTokenAccessor;
private static readonly Func<CSharpSyntaxNode, SyntaxToken, CSharpSyntaxNode> WithOpenParenTokenAccessor;
private static readonly Func<CSharpSyntaxNode, SeparatedSyntaxListWrapper<VariableDesignationSyntaxWrapper>, CSharpSyntaxNode> WithVariablesAccessor;
private static readonly Func<CSharpSyntaxNode, SyntaxToken, CSharpSyntaxNode> WithCloseParenTokenAccessor;

private readonly CSharpSyntaxNode node;
Expand All @@ -24,8 +26,10 @@ static ParenthesizedVariableDesignationSyntaxWrapper()
{
ParenthesizedVariableDesignationSyntaxType = typeof(CSharpSyntaxNode).GetTypeInfo().Assembly.GetType(ParenthesizedVariableDesignationSyntaxTypeName);
OpenParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor<CSharpSyntaxNode, SyntaxToken>(ParenthesizedVariableDesignationSyntaxType, nameof(OpenParenToken));
VariablesAccessor = LightupHelpers.CreateSeparatedSyntaxListPropertyAccessor<CSharpSyntaxNode, VariableDesignationSyntaxWrapper>(ParenthesizedVariableDesignationSyntaxType, nameof(Variables));
CloseParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor<CSharpSyntaxNode, SyntaxToken>(ParenthesizedVariableDesignationSyntaxType, nameof(CloseParenToken));
WithOpenParenTokenAccessor = LightupHelpers.CreateSyntaxWithPropertyAccessor<CSharpSyntaxNode, SyntaxToken>(ParenthesizedVariableDesignationSyntaxType, nameof(OpenParenToken));
WithVariablesAccessor = LightupHelpers.CreateSeparatedSyntaxListWithPropertyAccessor<CSharpSyntaxNode, VariableDesignationSyntaxWrapper>(ParenthesizedVariableDesignationSyntaxType, nameof(Variables));
WithCloseParenTokenAccessor = LightupHelpers.CreateSyntaxWithPropertyAccessor<CSharpSyntaxNode, SyntaxToken>(ParenthesizedVariableDesignationSyntaxType, nameof(CloseParenToken));
}

Expand All @@ -44,11 +48,11 @@ public SyntaxToken OpenParenToken
}
}

public SeparatedSyntaxList<CSharpSyntaxNode> Variables
public SeparatedSyntaxListWrapper<VariableDesignationSyntaxWrapper> Variables
{
get
{
throw new NotImplementedException();
return VariablesAccessor(this.SyntaxNode);
}
}

Expand Down Expand Up @@ -100,9 +104,9 @@ public ParenthesizedVariableDesignationSyntaxWrapper WithOpenParenToken(SyntaxTo
return new ParenthesizedVariableDesignationSyntaxWrapper(WithOpenParenTokenAccessor(this.SyntaxNode, identifier));
}

public ParenthesizedVariableDesignationSyntaxWrapper WithVariables(SeparatedSyntaxList<CSharpSyntaxNode> variables)
public ParenthesizedVariableDesignationSyntaxWrapper WithVariables(SeparatedSyntaxListWrapper<VariableDesignationSyntaxWrapper> variables)
{
throw new NotImplementedException();
return new ParenthesizedVariableDesignationSyntaxWrapper(WithVariablesAccessor(this.SyntaxNode, variables));
}

public ParenthesizedVariableDesignationSyntaxWrapper WithCloseParenToken(SyntaxToken identifier)
Expand Down
Loading

0 comments on commit ffc775f

Please sign in to comment.