Skip to content

Commit ed8974f

Browse files
committed
Implement light-up support for SeparatedSyntaxList<T>
1 parent afb97e4 commit ed8974f

8 files changed

Lines changed: 617 additions & 6 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/Lightup/ParenthesizedVariableDesignationSyntaxWrapperTests.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace StyleCop.Analyzers.Test.CSharp7.Lightup
66
using System;
77
using Microsoft.CodeAnalysis;
88
using Microsoft.CodeAnalysis.CSharp;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
910
using StyleCop.Analyzers.Helpers;
1011
using StyleCop.Analyzers.Lightup;
1112
using Xunit;
@@ -19,7 +20,11 @@ public void TestNull()
1920
var parenthesizedVariableDesignationSyntax = (ParenthesizedVariableDesignationSyntaxWrapper)syntaxNode;
2021
Assert.Null(parenthesizedVariableDesignationSyntax.SyntaxNode);
2122
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.OpenParenToken);
23+
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.Variables);
2224
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.CloseParenToken);
25+
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.WithOpenParenToken(SyntaxFactory.Token(SyntaxKind.OpenParenToken)));
26+
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.WithVariables(new SeparatedSyntaxListWrapper<VariableDesignationSyntaxWrapper>.AutoWrapSeparatedSyntaxList<VariableDesignationSyntax>(SyntaxFactory.SeparatedList<VariableDesignationSyntax>())));
27+
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.WithCloseParenToken(SyntaxFactory.Token(SyntaxKind.CloseParenToken)));
2328
}
2429

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

47+
var variables = SyntaxFactory.SingletonSeparatedList<VariableDesignationSyntax>(SyntaxFactory.DiscardDesignation());
48+
parenthesizedVariableDesignationSyntax = parenthesizedVariableDesignationSyntax.WithVariables(new SeparatedSyntaxListWrapper<VariableDesignationSyntaxWrapper>.AutoWrapSeparatedSyntaxList<VariableDesignationSyntax>(variables));
49+
Assert.Same(
50+
((ParenthesizedVariableDesignationSyntax)parenthesizedVariableDesignationSyntax.SyntaxNode).Variables[0],
51+
parenthesizedVariableDesignationSyntax.Variables[0].SyntaxNode);
52+
4253
parenthesizedVariableDesignationSyntax = parenthesizedVariableDesignationSyntax.WithCloseParenToken(SpacingExtensions.WithoutTrivia(SyntaxFactory.Token(SyntaxKind.CloseParenToken)));
4354
Assert.NotNull(parenthesizedVariableDesignationSyntax.SyntaxNode);
4455
Assert.False(syntaxNode.OpenParenToken.IsEquivalentTo(parenthesizedVariableDesignationSyntax.OpenParenToken));

StyleCop.Analyzers/StyleCop.Analyzers.Test/Lightup/ParenthesizedVariableDesignationSyntaxWrapperTests.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ public void TestNull()
1818
var parenthesizedVariableDesignationSyntax = (ParenthesizedVariableDesignationSyntaxWrapper)syntaxNode;
1919
Assert.Null(parenthesizedVariableDesignationSyntax.SyntaxNode);
2020
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.OpenParenToken);
21+
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.Variables);
2122
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.CloseParenToken);
23+
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.WithOpenParenToken(SyntaxFactory.Token(SyntaxKind.OpenParenToken)));
24+
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.WithVariables(new SeparatedSyntaxListWrapper<VariableDesignationSyntaxWrapper>.UnsupportedSyntaxList()));
25+
Assert.Throws<NullReferenceException>(() => parenthesizedVariableDesignationSyntax.WithCloseParenToken(SyntaxFactory.Token(SyntaxKind.CloseParenToken)));
2226
}
2327

2428
[Fact]

StyleCop.Analyzers/StyleCop.Analyzers/Lightup/CSharp7.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,10 +197,10 @@
197197
* [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.CloseParenToken.get -> Microsoft.CodeAnalysis.SyntaxToken`
198198
* [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.OpenParenToken.get -> Microsoft.CodeAnalysis.SyntaxToken`
199199
* [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken openParenToken, Microsoft.CodeAnalysis.SeparatedSyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax> variables, Microsoft.CodeAnalysis.SyntaxToken closeParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax`~~
200-
* [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.Variables.get -> Microsoft.CodeAnalysis.SeparatedSyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax>`
200+
* [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.Variables.get -> Microsoft.CodeAnalysis.SeparatedSyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax>`
201201
* [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.WithCloseParenToken(Microsoft.CodeAnalysis.SyntaxToken closeParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax`
202202
* [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.WithOpenParenToken(Microsoft.CodeAnalysis.SyntaxToken openParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax`
203-
* [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.WithVariables(Microsoft.CodeAnalysis.SeparatedSyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax> variables) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax`
203+
* [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.WithVariables(Microsoft.CodeAnalysis.SeparatedSyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax> variables) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax`
204204
* [x] `Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax`
205205
* [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax`
206206
* [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax.Expression.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax`

StyleCop.Analyzers/StyleCop.Analyzers/Lightup/LightupHelpers.cs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,62 @@ internal static Func<TSyntax, TProperty> CreateSyntaxPropertyAccessor<TSyntax, T
9696
return expression.Compile();
9797
}
9898

99+
internal static Func<TSyntax, SeparatedSyntaxListWrapper<TProperty>> CreateSeparatedSyntaxListPropertyAccessor<TSyntax, TProperty>(Type type, string propertyName)
100+
{
101+
Func<TSyntax, SeparatedSyntaxListWrapper<TProperty>> fallbackAccessor =
102+
syntax =>
103+
{
104+
if (syntax == null)
105+
{
106+
// Unlike an extension method which would throw ArgumentNullException here, the light-up
107+
// behavior needs to match behavior of the underlying property.
108+
throw new NullReferenceException();
109+
}
110+
111+
return SeparatedSyntaxListWrapper<TProperty>.UnsupportedEmpty;
112+
};
113+
114+
if (type == null)
115+
{
116+
return fallbackAccessor;
117+
}
118+
119+
if (!typeof(TSyntax).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
120+
{
121+
throw new InvalidOperationException();
122+
}
123+
124+
var property = type.GetTypeInfo().GetDeclaredProperty(propertyName);
125+
if (property == null)
126+
{
127+
return fallbackAccessor;
128+
}
129+
130+
if (property.PropertyType.GetGenericTypeDefinition() != typeof(SeparatedSyntaxList<>))
131+
{
132+
throw new InvalidOperationException();
133+
}
134+
135+
var propertySyntaxType = property.PropertyType.GenericTypeArguments[0];
136+
137+
var syntaxParameter = Expression.Parameter(typeof(TSyntax), "syntax");
138+
Expression instance =
139+
type.GetTypeInfo().IsAssignableFrom(typeof(TSyntax).GetTypeInfo())
140+
? (Expression)syntaxParameter
141+
: Expression.Convert(syntaxParameter, type);
142+
Expression propertyAccess = Expression.Call(instance, property.GetMethod);
143+
144+
var unboundWrapperType = typeof(SeparatedSyntaxListWrapper<>.AutoWrapSeparatedSyntaxList<>);
145+
var boundWrapperType = unboundWrapperType.MakeGenericType(typeof(TProperty), propertySyntaxType);
146+
var constructorInfo = boundWrapperType.GetTypeInfo().DeclaredConstructors.Single();
147+
148+
Expression<Func<TSyntax, SeparatedSyntaxListWrapper<TProperty>>> expression =
149+
Expression.Lambda<Func<TSyntax, SeparatedSyntaxListWrapper<TProperty>>>(
150+
Expression.New(constructorInfo, propertyAccess),
151+
syntaxParameter);
152+
return expression.Compile();
153+
}
154+
99155
internal static Func<TSyntax, TProperty, TSyntax> CreateSyntaxWithPropertyAccessor<TSyntax, TProperty>(Type type, string propertyName)
100156
{
101157
Func<TSyntax, TProperty, TSyntax> fallbackAccessor =
@@ -158,5 +214,71 @@ internal static Func<TSyntax, TProperty, TSyntax> CreateSyntaxWithPropertyAccess
158214
valueParameter);
159215
return expression.Compile();
160216
}
217+
218+
internal static Func<TSyntax, SeparatedSyntaxListWrapper<TProperty>, TSyntax> CreateSeparatedSyntaxListWithPropertyAccessor<TSyntax, TProperty>(Type type, string propertyName)
219+
{
220+
Func<TSyntax, SeparatedSyntaxListWrapper<TProperty>, TSyntax> fallbackAccessor =
221+
(syntax, newValue) =>
222+
{
223+
if (syntax == null)
224+
{
225+
// Unlike an extension method which would throw ArgumentNullException here, the light-up
226+
// behavior needs to match behavior of the underlying property.
227+
throw new NullReferenceException();
228+
}
229+
230+
if (ReferenceEquals(newValue, null))
231+
{
232+
return syntax;
233+
}
234+
235+
throw new NotSupportedException();
236+
};
237+
238+
if (type == null)
239+
{
240+
return fallbackAccessor;
241+
}
242+
243+
if (!typeof(TSyntax).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
244+
{
245+
throw new InvalidOperationException();
246+
}
247+
248+
var property = type.GetTypeInfo().GetDeclaredProperty(propertyName);
249+
if (property == null)
250+
{
251+
return fallbackAccessor;
252+
}
253+
254+
if (property.PropertyType.GetGenericTypeDefinition() != typeof(SeparatedSyntaxList<>))
255+
{
256+
throw new InvalidOperationException();
257+
}
258+
259+
var propertySyntaxType = property.PropertyType.GenericTypeArguments[0];
260+
261+
var methodInfo = type.GetTypeInfo().GetDeclaredMethods("With" + propertyName)
262+
.Single(m => !m.IsStatic && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.Equals(property.PropertyType));
263+
264+
var syntaxParameter = Expression.Parameter(typeof(TSyntax), "syntax");
265+
var valueParameter = Expression.Parameter(typeof(SeparatedSyntaxListWrapper<TProperty>), methodInfo.GetParameters()[0].Name);
266+
Expression instance =
267+
type.GetTypeInfo().IsAssignableFrom(typeof(TSyntax).GetTypeInfo())
268+
? (Expression)syntaxParameter
269+
: Expression.Convert(syntaxParameter, type);
270+
271+
var underlyingListProperty = typeof(SeparatedSyntaxListWrapper<TProperty>).GetTypeInfo().GetDeclaredProperty(nameof(SeparatedSyntaxListWrapper<TProperty>.UnderlyingList));
272+
Expression value = Expression.Convert(
273+
Expression.Call(valueParameter, underlyingListProperty.GetMethod),
274+
property.PropertyType);
275+
276+
Expression<Func<TSyntax, SeparatedSyntaxListWrapper<TProperty>, TSyntax>> expression =
277+
Expression.Lambda<Func<TSyntax, SeparatedSyntaxListWrapper<TProperty>, TSyntax>>(
278+
Expression.Call(instance, methodInfo, value),
279+
syntaxParameter,
280+
valueParameter);
281+
return expression.Compile();
282+
}
161283
}
162284
}

StyleCop.Analyzers/StyleCop.Analyzers/Lightup/ParenthesizedVariableDesignationSyntaxWrapper.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ internal struct ParenthesizedVariableDesignationSyntaxWrapper : ISyntaxWrapper<C
1414
private static readonly Type ParenthesizedVariableDesignationSyntaxType;
1515

1616
private static readonly Func<CSharpSyntaxNode, SyntaxToken> OpenParenTokenAccessor;
17+
private static readonly Func<CSharpSyntaxNode, SeparatedSyntaxListWrapper<VariableDesignationSyntaxWrapper>> VariablesAccessor;
1718
private static readonly Func<CSharpSyntaxNode, SyntaxToken> CloseParenTokenAccessor;
1819
private static readonly Func<CSharpSyntaxNode, SyntaxToken, CSharpSyntaxNode> WithOpenParenTokenAccessor;
20+
private static readonly Func<CSharpSyntaxNode, SeparatedSyntaxListWrapper<VariableDesignationSyntaxWrapper>, CSharpSyntaxNode> WithVariablesAccessor;
1921
private static readonly Func<CSharpSyntaxNode, SyntaxToken, CSharpSyntaxNode> WithCloseParenTokenAccessor;
2022

2123
private readonly CSharpSyntaxNode node;
@@ -24,8 +26,10 @@ static ParenthesizedVariableDesignationSyntaxWrapper()
2426
{
2527
ParenthesizedVariableDesignationSyntaxType = typeof(CSharpSyntaxNode).GetTypeInfo().Assembly.GetType(ParenthesizedVariableDesignationSyntaxTypeName);
2628
OpenParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor<CSharpSyntaxNode, SyntaxToken>(ParenthesizedVariableDesignationSyntaxType, nameof(OpenParenToken));
29+
VariablesAccessor = LightupHelpers.CreateSeparatedSyntaxListPropertyAccessor<CSharpSyntaxNode, VariableDesignationSyntaxWrapper>(ParenthesizedVariableDesignationSyntaxType, nameof(Variables));
2730
CloseParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor<CSharpSyntaxNode, SyntaxToken>(ParenthesizedVariableDesignationSyntaxType, nameof(CloseParenToken));
2831
WithOpenParenTokenAccessor = LightupHelpers.CreateSyntaxWithPropertyAccessor<CSharpSyntaxNode, SyntaxToken>(ParenthesizedVariableDesignationSyntaxType, nameof(OpenParenToken));
32+
WithVariablesAccessor = LightupHelpers.CreateSeparatedSyntaxListWithPropertyAccessor<CSharpSyntaxNode, VariableDesignationSyntaxWrapper>(ParenthesizedVariableDesignationSyntaxType, nameof(Variables));
2933
WithCloseParenTokenAccessor = LightupHelpers.CreateSyntaxWithPropertyAccessor<CSharpSyntaxNode, SyntaxToken>(ParenthesizedVariableDesignationSyntaxType, nameof(CloseParenToken));
3034
}
3135

@@ -44,11 +48,11 @@ public SyntaxToken OpenParenToken
4448
}
4549
}
4650

47-
public SeparatedSyntaxList<CSharpSyntaxNode> Variables
51+
public SeparatedSyntaxListWrapper<VariableDesignationSyntaxWrapper> Variables
4852
{
4953
get
5054
{
51-
throw new NotImplementedException();
55+
return VariablesAccessor(this.SyntaxNode);
5256
}
5357
}
5458

@@ -100,9 +104,9 @@ public ParenthesizedVariableDesignationSyntaxWrapper WithOpenParenToken(SyntaxTo
100104
return new ParenthesizedVariableDesignationSyntaxWrapper(WithOpenParenTokenAccessor(this.SyntaxNode, identifier));
101105
}
102106

103-
public ParenthesizedVariableDesignationSyntaxWrapper WithVariables(SeparatedSyntaxList<CSharpSyntaxNode> variables)
107+
public ParenthesizedVariableDesignationSyntaxWrapper WithVariables(SeparatedSyntaxListWrapper<VariableDesignationSyntaxWrapper> variables)
104108
{
105-
throw new NotImplementedException();
109+
return new ParenthesizedVariableDesignationSyntaxWrapper(WithVariablesAccessor(this.SyntaxNode, variables));
106110
}
107111

108112
public ParenthesizedVariableDesignationSyntaxWrapper WithCloseParenToken(SyntaxToken identifier)

0 commit comments

Comments
 (0)