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

Highlight TypedConstant values in editor #84

Merged
merged 2 commits into from
Aug 6, 2024
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 @@ -163,7 +163,7 @@ private void DestroyChildrenFromSecondLevel()
if (parentNode is null)
return current;

if (current.NodeLine.AnalysisNodeKind != targetKind)
if (MatchesNodeKind(current.NodeLine.AnalysisNodeKind, targetKind))
{
goto next;
}
Expand All @@ -182,6 +182,18 @@ private void DestroyChildrenFromSecondLevel()
}
}

private static bool MatchesNodeKind(AnalysisNodeKind value, AnalysisNodeKind target)
{
if (target is AnalysisNodeKind.Symbol)
{
return value
is AnalysisNodeKind.Attribute
or AnalysisNodeKind.Symbol;
}

return value == target;
}

protected override void OnPointerExited(PointerEventArgs e)
{
base.OnPointerExited(e);
Expand Down Expand Up @@ -408,7 +420,7 @@ private async Task AwaitExpansion(AnalysisTreeListNode node)
if (current is null)
return null;

if (current.NodeLine.AnalysisNodeKind == TargetAnalysisNodeKind)
if (MatchesNodeKind(current.NodeLine.AnalysisNodeKind, TargetAnalysisNodeKind))
{
return current;
}
Expand Down Expand Up @@ -459,7 +471,7 @@ private async Task AwaitExpansion(AnalysisTreeListNode node)
.Where(s =>
{
var nodeLine = s.NodeLine;
return nodeLine.AnalysisNodeKind == TargetAnalysisNodeKind
return MatchesNodeKind(nodeLine.AnalysisNodeKind, TargetAnalysisNodeKind)
&& nodeLine.DisplaySpan.Contains(span);
})
.ToArray();
Expand Down
176 changes: 176 additions & 0 deletions Syndiesis/Core/AttributeDataViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.VisualBasic;
using System.Collections.Immutable;
using System.Threading;

namespace Syndiesis.Core;

public sealed class AttributeDataViewModel
{
public required AttributeData AttributeData { get; init; }
public required ImmutableArray<LinkedAttributeArgument> ConstructorArguments { get; init; }
public required ImmutableArray<LinkedAttributeArgument> NamedArguments { get; init; }

public ImmutableArray<LinkedAttributeArgument> AllArguments
{
get
{
return
[
.. ConstructorArguments,
.. NamedArguments,
];
}
}

private AttributeDataViewModel() { }

public static AttributeDataViewModel? Create(
AttributeData data,
CancellationToken cancellationToken = default)
{
var syntax = data.ApplicationSyntaxReference?.GetSyntax(cancellationToken);
if (syntax is null)
return null;

if (data.HasNoArguments())
return Empty(data);

switch (syntax)
{
case CSharpSyntaxNode csAttribute:
return CreateCSharpModel(data, csAttribute);
case VisualBasicSyntaxNode vbAttribute:
return CreateVisualBasicModel(data, vbAttribute);
default:
return null;
}
}

private static AttributeDataViewModel Empty(AttributeData data)
{
return new()
{
AttributeData = data,
ConstructorArguments = [],
NamedArguments = [],
};
}

private static AttributeDataViewModel? CreateCSharpModel(
AttributeData data, CSharpSyntaxNode csAttribute)
{
var attribute = csAttribute as Microsoft.CodeAnalysis.CSharp.Syntax.AttributeSyntax;
if (attribute is null)
return null;

var argumentList = attribute.ArgumentList;
if (argumentList is null)
return Empty(data);

var arguments = argumentList.Arguments;
if (arguments is [])
return Empty(data);

return ConstructFromArguments(data, arguments);
}

private static AttributeDataViewModel? CreateVisualBasicModel(
AttributeData data, VisualBasicSyntaxNode vbAttribute)
{
var attribute = vbAttribute as Microsoft.CodeAnalysis.VisualBasic.Syntax.AttributeSyntax;
if (attribute is null)
return null;

var argumentList = attribute.ArgumentList;
if (argumentList is null)
return Empty(data);

var arguments = argumentList.Arguments;
if (arguments is [])
return Empty(data);

return ConstructFromArguments(data, arguments);
}

private static AttributeDataViewModel ConstructFromArguments<TSyntax>(
AttributeData data, SeparatedSyntaxList<TSyntax> arguments)
where TSyntax : SyntaxNode
{
ImmutableArray<LinkedAttributeArgument> regularArguments = [];
ImmutableArray<LinkedAttributeArgument> namedArguments = [];

if (data.ConstructorArguments is not [] and var constructorArgumentsData)
{
var regularArgumentsBuilder = ImmutableArray.CreateBuilder<LinkedAttributeArgument>(
constructorArgumentsData.Length);

// We always have a constructor if we have constructor arguments
// We expect a potential breaking API change that returns a null
// constructor with a non-empty argument list, in which case we show the param order
var constructor = data.AttributeConstructor;
var constructorParameters = constructor?.Parameters;
var mappingKind =
constructorParameters is not null
? AttributeArgumentNameMappingKind.Parameter
: AttributeArgumentNameMappingKind.ParameterIndex;
for (int i = 0; i < constructorArgumentsData.Length; i++)
{
var argumentSyntax = arguments[i];
var value = constructorArgumentsData[i];
var parameter = constructorParameters?[i];
var displayName = parameter?.Name ?? i.ToString();
regularArgumentsBuilder.Add(new(
argumentSyntax,
displayName,
value,
mappingKind));
}
regularArguments = regularArgumentsBuilder.ToImmutable();
}

if (data.NamedArguments is not [] and var namedArgumentsData)
{
var namedArgumentsBuilder = ImmutableArray.CreateBuilder<LinkedAttributeArgument>(
namedArgumentsData.Length);

int offset = data.ConstructorArguments.Length;

for (int i = 0; i < namedArgumentsData.Length; i++)
{
var argumentSyntax = arguments[i + offset];
var kvp = namedArgumentsData[i];
var name = kvp.Key;
var value = kvp.Value;
namedArgumentsBuilder.Add(new(
argumentSyntax,
name,
value,
AttributeArgumentNameMappingKind.Named));
}
namedArguments = namedArgumentsBuilder.ToImmutable();
}

return new()
{
AttributeData = data,
ConstructorArguments = regularArguments,
NamedArguments = namedArguments,
};
}

public sealed record class LinkedAttributeArgument(
SyntaxNode ArgumentSyntax,
string Name,
TypedConstant Value,
AttributeArgumentNameMappingKind MappingKind)
;

public enum AttributeArgumentNameMappingKind
{
Parameter,
ParameterIndex,
Named,
}
}
17 changes: 11 additions & 6 deletions Syndiesis/Core/AttributeTree.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.CodeAnalysis;
using Garyon.Functions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.VisualBasic;
Expand Down Expand Up @@ -121,17 +122,21 @@ ImmutableArray<AttributeDataView> DataViews(
ImmutableArray<AttributeData> data)
{
return data.Select(FromAttribute)
.ToImmutableArray();
.Where(Predicates.NotNull)
.ToImmutableArray()!;
}

AttributeDataView FromAttribute(AttributeData data)
AttributeDataView? FromAttribute(AttributeData data)
{
var model = AttributeDataViewModel.Create(data);
if (model is null)
return null;
var syntax = data.ApplicationSyntaxReference?.GetSyntax(cancellationToken);
if (syntax is null)
return new(data, null);
return new(model, null);

var operation = semanticModel.GetOperation(syntax, cancellationToken);
return new AttributeDataView(data, operation as IAttributeOperation);
return new AttributeDataView(model, operation as IAttributeOperation);
}
}

Expand Down Expand Up @@ -220,5 +225,5 @@ public sealed record SymbolContainer(
ISymbol Symbol, ImmutableArray<AttributeDataView> Attributes);

public sealed record AttributeDataView(
AttributeData Data, IAttributeOperation? AttributeOperation);
AttributeDataViewModel Data, IAttributeOperation? AttributeOperation);
}
Loading
Loading