using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using XFEExtension.NetCore.AutoConfig.Diagnostics;
namespace XFEExtension.NetCore.AutoConfig.CodeFix;
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AutoConfigCodeFixProvider)), Shared]
public sealed class AutoConfigCodeFixProvider : CodeFixProvider
{
private const string ProfilePropertyAttributeName = "XFEExtension.NetCore.AutoConfig.ProfilePropertyAttribute";
private const string AddGetAttributeName = "XFEExtension.NetCore.AutoConfig.ProfilePropertyAddGetAttribute";
private const string AddSetAttributeName = "XFEExtension.NetCore.AutoConfig.ProfilePropertyAddSetAttribute";
public override ImmutableArray<string> FixableDiagnosticIds => [AutoConfigDiagnostics.AddGetNoResultErrorId, AutoConfigDiagnostics.AddSetNoSetResultWarningId, AutoConfigDiagnostics.FieldCanUsePartialPropertyId];
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
foreach (var diagnostic in context.Diagnostics)
{
if (diagnostic.Id == AutoConfigDiagnostics.AddGetNoResultErrorId)
{
context.RegisterCodeFix(
CodeAction.Create("添加返回值方法", cancellationToken => AddAttributeAsync(context.Document, diagnostic.Location.SourceSpan, "ProfilePropertyAddGet", "return Current.{0}", cancellationToken), "添加返回值"),
diagnostic);
}
else if (diagnostic.Id == AutoConfigDiagnostics.AddSetNoSetResultWarningId)
{
context.RegisterCodeFix(
CodeAction.Create("添加字段的设置方法", cancellationToken => AddAttributeAsync(context.Document, diagnostic.Location.SourceSpan, "ProfilePropertyAddSet", "Current.{0} = value", cancellationToken), "添加字段的设置方法"),
diagnostic);
}
else if (diagnostic.Id == AutoConfigDiagnostics.FieldCanUsePartialPropertyId
&& diagnostic.Properties.TryGetValue("PropertyName", out var propertyName)
&& !string.IsNullOrWhiteSpace(propertyName))
{
context.RegisterCodeFix(
CodeAction.Create(
"转换为 .NET 10 部分配置属性",
cancellationToken => ConvertToPartialPropertyAsync(context.Document, diagnostic.Location.SourceSpan, propertyName!, cancellationToken),
"转换为部分配置属性"),
diagnostic);
}
}
return Task.CompletedTask;
}
private static async Task<Document> AddAttributeAsync(Document document, TextSpan sourceSpan, string attributeName, string codeFormat, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
if (root is null)
return document;
var diagnosticNode = root.FindToken(sourceSpan.Start).Parent;
var fieldDeclaration = diagnosticNode?.AncestorsAndSelf().OfType<FieldDeclarationSyntax>().FirstOrDefault();
var propertyDeclaration = diagnosticNode?.AncestorsAndSelf().OfType<PropertyDeclarationSyntax>().FirstOrDefault();
if (fieldDeclaration is null && propertyDeclaration is null)
return document;
var code = propertyDeclaration is null
? string.Format(System.Globalization.CultureInfo.InvariantCulture, codeFormat, fieldDeclaration!.Declaration.Variables.First().Identifier.ValueText)
: attributeName == AddGetAttributeName || attributeName == "ProfilePropertyAddGet"
? "return field"
: "field = value";
var newAttribute = SyntaxFactory.Attribute(SyntaxFactory.ParseName(attributeName))
.AddArgumentListArguments(SyntaxFactory.AttributeArgument(SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(code))));
var attributeList = SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList(newAttribute));
var newRoot = fieldDeclaration is not null
? root.ReplaceNode(fieldDeclaration, fieldDeclaration.AddAttributeLists(attributeList))
: root.ReplaceNode(propertyDeclaration!, propertyDeclaration!.AddAttributeLists(attributeList));
return document.WithSyntaxRoot(newRoot);
}
private static async Task<Solution> ConvertToPartialPropertyAsync(Document document, TextSpan sourceSpan, string propertyName, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
if (root is null || semanticModel is null)
return document.Project.Solution;
var variable = root.FindToken(sourceSpan.Start).Parent?.AncestorsAndSelf().OfType<VariableDeclaratorSyntax>().FirstOrDefault();
var fieldDeclaration = variable?.Parent?.Parent as FieldDeclarationSyntax;
if (variable is null
|| fieldDeclaration is null
|| fieldDeclaration.Declaration.Variables.Count != 1
|| semanticModel.GetDeclaredSymbol(variable, cancellationToken) is not IFieldSymbol field)
return document.Project.Solution;
var originalFieldName = field.Name;
var profileAttributeNames = new HashSet<string>(StringComparer.Ordinal);
var getHookAttributeNames = new HashSet<string>(StringComparer.Ordinal);
var setHookAttributeNames = new HashSet<string>(StringComparer.Ordinal);
foreach (var attribute in fieldDeclaration.AttributeLists.SelectMany(static list => list.Attributes))
{
var attributeTypeName = (semanticModel.GetSymbolInfo(attribute, cancellationToken).Symbol as IMethodSymbol)?.ContainingType.ToDisplayString();
if (attributeTypeName != ProfilePropertyAttributeName && attributeTypeName != AddGetAttributeName && attributeTypeName != AddSetAttributeName)
continue;
var sourceName = attribute.Name.ToString();
profileAttributeNames.Add(sourceName);
if (attributeTypeName == AddGetAttributeName)
getHookAttributeNames.Add(sourceName);
else if (attributeTypeName == AddSetAttributeName)
setHookAttributeNames.Add(sourceName);
}
var solution = document.Project.Solution;
var declarationAnnotation = new SyntaxAnnotation();
solution = solution.WithDocumentSyntaxRoot(
document.Id,
root.ReplaceNode(fieldDeclaration, fieldDeclaration.WithAdditionalAnnotations(declarationAnnotation)));
var referencedSymbols = await SymbolFinder.FindReferencesAsync(field, solution, cancellationToken).ConfigureAwait(false);
var referenceLocations = referencedSymbols
.SelectMany(static referencedSymbol => referencedSymbol.Locations)
.Where(static location => location.Location.IsInSource)
.GroupBy(static location => location.Document.Id);
foreach (var documentLocations in referenceLocations)
{
var referenceDocument = solution.GetDocument(documentLocations.Key);
var referenceRoot = referenceDocument is null ? null : await referenceDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
if (referenceDocument is null || referenceRoot is null)
continue;
var tokens = documentLocations
.Select(location => referenceRoot.FindToken(location.Location.SourceSpan.Start))
.Where(token => token.IsKind(SyntaxKind.IdentifierToken) && token.ValueText == originalFieldName)
.Distinct()
.ToArray();
if (tokens.Length == 0)
continue;
var renamedRoot = referenceRoot.ReplaceTokens(tokens, (_, rewritten) => SyntaxFactory.Identifier(rewritten.LeadingTrivia, propertyName, rewritten.TrailingTrivia));
solution = solution.WithDocumentSyntaxRoot(referenceDocument.Id, renamedRoot);
}
var declarationDocument = solution.GetDocument(document.Id);
var declarationRoot = declarationDocument is null ? null : await declarationDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
if (declarationDocument is null || declarationRoot is null)
return solution;
var currentFieldDeclaration = declarationRoot.GetAnnotatedNodes(declarationAnnotation)
.OfType<FieldDeclarationSyntax>()
.Where(static candidate => candidate.Declaration.Variables.Count == 1)
.FirstOrDefault(candidate => candidate.Declaration.Variables[0].Identifier.ValueText == originalFieldName);
if (currentFieldDeclaration is null)
return solution;
var currentVariable = currentFieldDeclaration.Declaration.Variables[0];
var attributeLists = PreparePartialPropertyAttributes(
currentFieldDeclaration.AttributeLists,
originalFieldName,
"field",
profileAttributeNames,
getHookAttributeNames,
setHookAttributeNames);
var partialProperty = SyntaxFactory.PropertyDeclaration(currentFieldDeclaration.Declaration.Type.WithoutTrivia(), SyntaxFactory.Identifier(propertyName))
.WithAttributeLists(attributeLists)
.WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.PartialKeyword)))
.WithAccessorList(SyntaxFactory.AccessorList(SyntaxFactory.List([
SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))
])))
.WithInitializer(currentVariable.Initializer)
.WithSemicolonToken(currentVariable.Initializer is null ? default : SyntaxFactory.Token(SyntaxKind.SemicolonToken))
.WithLeadingTrivia(currentFieldDeclaration.GetLeadingTrivia())
.WithTrailingTrivia(currentFieldDeclaration.GetTrailingTrivia())
.WithAdditionalAnnotations(Formatter.Annotation);
var updatedRoot = declarationRoot.ReplaceNode(currentFieldDeclaration, partialProperty);
return solution.WithDocumentSyntaxRoot(declarationDocument.Id, updatedRoot);
}
private static SyntaxList<AttributeListSyntax> RewriteHookAttributeStrings(
SyntaxList<AttributeListSyntax> attributeLists,
string fieldName,
string backingFieldName,
HashSet<string> getHookAttributeNames,
HashSet<string> setHookAttributeNames)
{
return SyntaxFactory.List(attributeLists.Select(attributeList => attributeList.ReplaceNodes(
attributeList.Attributes,
(_, attribute) =>
{
var attributeName = attribute.Name.ToString();
var isGetHook = getHookAttributeNames.Contains(attributeName);
if (!isGetHook && !setHookAttributeNames.Contains(attributeName))
return attribute;
var arguments = attribute.ArgumentList?.Arguments;
if (arguments is not { Count: > 0 }
|| arguments.Value[0].Expression is not LiteralExpressionSyntax literal
|| !literal.IsKind(SyntaxKind.StringLiteralExpression))
return attribute;
var rewrittenCode = literal.Token.ValueText;
var qualifiedFieldPattern = $@"(?:(?:\b@?[A-Za-z_][A-Za-z0-9_]*\s*\.\s*)*)(?:Current|this)\s*\.\s*{Regex.Escape(fieldName)}\b";
rewrittenCode = Regex.Replace(rewrittenCode, qualifiedFieldPattern, backingFieldName);
if (isGetHook || fieldName != "value")
{
rewrittenCode = Regex.Replace(rewrittenCode, $@"\b{Regex.Escape(fieldName)}\b", backingFieldName);
}
var rewrittenLiteral = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(rewrittenCode));
return attribute.ReplaceNode(literal, rewrittenLiteral);
})));
}
private static SyntaxList<AttributeListSyntax> PreparePartialPropertyAttributes(
SyntaxList<AttributeListSyntax> attributeLists,
string fieldName,
string backingFieldName,
HashSet<string> profileAttributeNames,
HashSet<string> getHookAttributeNames,
HashSet<string> setHookAttributeNames)
{
var rewrittenLists = RewriteHookAttributeStrings(attributeLists, fieldName, backingFieldName, getHookAttributeNames, setHookAttributeNames);
var result = new List<AttributeListSyntax>();
foreach (var attributeList in rewrittenLists)
{
var propertyAttributes = attributeList.Attributes.Where(attribute => profileAttributeNames.Contains(attribute.Name.ToString())).ToArray();
var backingFieldAttributes = attributeList.Attributes.Where(attribute => !profileAttributeNames.Contains(attribute.Name.ToString())).ToArray();
if (propertyAttributes.Length > 0)
{
result.Add(attributeList
.WithTarget(null)
.WithAttributes(SyntaxFactory.SeparatedList(propertyAttributes)));
}
if (backingFieldAttributes.Length > 0)
{
result.Add(attributeList
.WithTarget(SyntaxFactory.AttributeTargetSpecifier(SyntaxFactory.Token(SyntaxKind.FieldKeyword))
.WithColonToken(SyntaxFactory.Token(SyntaxKind.ColonToken)))
.WithAttributes(SyntaxFactory.SeparatedList(backingFieldAttributes))
.WithLeadingTrivia(propertyAttributes.Length == 0 ? attributeList.GetLeadingTrivia() : default));
}
}
return SyntaxFactory.List(result);
}
}
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using XFEExtension.NetCore.AutoConfig.Diagnostics;
namespace XFEExtension.NetCore.AutoConfig.CodeFix;
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AutoConfigCodeFixProvider)), Shared]
public sealed class AutoConfigCodeFixProvider : CodeFixProvider
{
private const string ProfilePropertyAttributeName = "XFEExtension.NetCore.AutoConfig.ProfilePropertyAttribute";
private const string AddGetAttributeName = "XFEExtension.NetCore.AutoConfig.ProfilePropertyAddGetAttribute";
private const string AddSetAttributeName = "XFEExtension.NetCore.AutoConfig.ProfilePropertyAddSetAttribute";
public override ImmutableArray<string> FixableDiagnosticIds => [AutoConfigDiagnostics.AddGetNoResultErrorId, AutoConfigDiagnostics.AddSetNoSetResultWarningId, AutoConfigDiagnostics.FieldCanUsePartialPropertyId];
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
foreach (var diagnostic in context.Diagnostics)
{
if (diagnostic.Id == AutoConfigDiagnostics.AddGetNoResultErrorId)
{
context.RegisterCodeFix(
CodeAction.Create("添加返回值方法", cancellationToken => AddAttributeAsync(context.Document, diagnostic.Location.SourceSpan, "ProfilePropertyAddGet", "return Current.{0}", cancellationToken), "添加返回值"),
diagnostic);
}
else if (diagnostic.Id == AutoConfigDiagnostics.AddSetNoSetResultWarningId)
{
context.RegisterCodeFix(
CodeAction.Create("添加字段的设置方法", cancellationToken => AddAttributeAsync(context.Document, diagnostic.Location.SourceSpan, "ProfilePropertyAddSet", "Current.{0} = value", cancellationToken), "添加字段的设置方法"),
diagnostic);
}
else if (diagnostic.Id == AutoConfigDiagnostics.FieldCanUsePartialPropertyId
&& diagnostic.Properties.TryGetValue("PropertyName", out var propertyName)
&& !string.IsNullOrWhiteSpace(propertyName))
{
context.RegisterCodeFix(
CodeAction.Create(
"转换为 .NET 10 部分配置属性",
cancellationToken => ConvertToPartialPropertyAsync(context.Document, diagnostic.Location.SourceSpan, propertyName!, cancellationToken),
"转换为部分配置属性"),
diagnostic);
}
}
return Task.CompletedTask;
}
private static async Task<Document> AddAttributeAsync(Document document, TextSpan sourceSpan, string attributeName, string codeFormat, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
if (root is null)
return document;
var diagnosticNode = root.FindToken(sourceSpan.Start).Parent;
var fieldDeclaration = diagnosticNode?.AncestorsAndSelf().OfType<FieldDeclarationSyntax>().FirstOrDefault();
var propertyDeclaration = diagnosticNode?.AncestorsAndSelf().OfType<PropertyDeclarationSyntax>().FirstOrDefault();
if (fieldDeclaration is null && propertyDeclaration is null)
return document;
var code = propertyDeclaration is null
? string.Format(System.Globalization.CultureInfo.InvariantCulture, codeFormat, fieldDeclaration!.Declaration.Variables.First().Identifier.ValueText)
: attributeName == AddGetAttributeName || attributeName == "ProfilePropertyAddGet"
? "return field"
: "field = value";
var newAttribute = SyntaxFactory.Attribute(SyntaxFactory.ParseName(attributeName))
.AddArgumentListArguments(SyntaxFactory.AttributeArgument(SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(code))));
var attributeList = SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList(newAttribute));
var newRoot = fieldDeclaration is not null
? root.ReplaceNode(fieldDeclaration, fieldDeclaration.AddAttributeLists(attributeList))
: root.ReplaceNode(propertyDeclaration!, propertyDeclaration!.AddAttributeLists(attributeList));
return document.WithSyntaxRoot(newRoot);
}
private static async Task<Solution> ConvertToPartialPropertyAsync(Document document, TextSpan sourceSpan, string propertyName, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
if (root is null || semanticModel is null)
return document.Project.Solution;
var variable = root.FindToken(sourceSpan.Start).Parent?.AncestorsAndSelf().OfType<VariableDeclaratorSyntax>().FirstOrDefault();
var fieldDeclaration = variable?.Parent?.Parent as FieldDeclarationSyntax;
if (variable is null
|| fieldDeclaration is null
|| fieldDeclaration.Declaration.Variables.Count != 1
|| semanticModel.GetDeclaredSymbol(variable, cancellationToken) is not IFieldSymbol field)
return document.Project.Solution;
var originalFieldName = field.Name;
var profileAttributeNames = new HashSet<string>(StringComparer.Ordinal);
var getHookAttributeNames = new HashSet<string>(StringComparer.Ordinal);
var setHookAttributeNames = new HashSet<string>(StringComparer.Ordinal);
foreach (var attribute in fieldDeclaration.AttributeLists.SelectMany(static list => list.Attributes))
{
var attributeTypeName = (semanticModel.GetSymbolInfo(attribute, cancellationToken).Symbol as IMethodSymbol)?.ContainingType.ToDisplayString();
if (attributeTypeName != ProfilePropertyAttributeName && attributeTypeName != AddGetAttributeName && attributeTypeName != AddSetAttributeName)
continue;
var sourceName = attribute.Name.ToString();
profileAttributeNames.Add(sourceName);
if (attributeTypeName == AddGetAttributeName)
getHookAttributeNames.Add(sourceName);
else if (attributeTypeName == AddSetAttributeName)
setHookAttributeNames.Add(sourceName);
}
var solution = document.Project.Solution;
var declarationAnnotation = new SyntaxAnnotation();
solution = solution.WithDocumentSyntaxRoot(
document.Id,
root.ReplaceNode(fieldDeclaration, fieldDeclaration.WithAdditionalAnnotations(declarationAnnotation)));
var referencedSymbols = await SymbolFinder.FindReferencesAsync(field, solution, cancellationToken).ConfigureAwait(false);
var referenceLocations = referencedSymbols
.SelectMany(static referencedSymbol => referencedSymbol.Locations)
.Where(static location => location.Location.IsInSource)
.GroupBy(static location => location.Document.Id);
foreach (var documentLocations in referenceLocations)
{
var referenceDocument = solution.GetDocument(documentLocations.Key);
var referenceRoot = referenceDocument is null ? null : await referenceDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
if (referenceDocument is null || referenceRoot is null)
continue;
var tokens = documentLocations
.Select(location => referenceRoot.FindToken(location.Location.SourceSpan.Start))
.Where(token => token.IsKind(SyntaxKind.IdentifierToken) && token.ValueText == originalFieldName)
.Distinct()
.ToArray();
if (tokens.Length == 0)
continue;
var renamedRoot = referenceRoot.ReplaceTokens(tokens, (_, rewritten) => SyntaxFactory.Identifier(rewritten.LeadingTrivia, propertyName, rewritten.TrailingTrivia));
solution = solution.WithDocumentSyntaxRoot(referenceDocument.Id, renamedRoot);
}
var declarationDocument = solution.GetDocument(document.Id);
var declarationRoot = declarationDocument is null ? null : await declarationDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
if (declarationDocument is null || declarationRoot is null)
return solution;
var currentFieldDeclaration = declarationRoot.GetAnnotatedNodes(declarationAnnotation)
.OfType<FieldDeclarationSyntax>()
.Where(static candidate => candidate.Declaration.Variables.Count == 1)
.FirstOrDefault(candidate => candidate.Declaration.Variables[0].Identifier.ValueText == originalFieldName);
if (currentFieldDeclaration is null)
return solution;
var currentVariable = currentFieldDeclaration.Declaration.Variables[0];
var attributeLists = PreparePartialPropertyAttributes(
currentFieldDeclaration.AttributeLists,
originalFieldName,
"field",
profileAttributeNames,
getHookAttributeNames,
setHookAttributeNames);
var partialProperty = SyntaxFactory.PropertyDeclaration(currentFieldDeclaration.Declaration.Type.WithoutTrivia(), SyntaxFactory.Identifier(propertyName))
.WithAttributeLists(attributeLists)
.WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.PartialKeyword)))
.WithAccessorList(SyntaxFactory.AccessorList(SyntaxFactory.List([
SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))
])))
.WithInitializer(currentVariable.Initializer)
.WithSemicolonToken(currentVariable.Initializer is null ? default : SyntaxFactory.Token(SyntaxKind.SemicolonToken))
.WithLeadingTrivia(currentFieldDeclaration.GetLeadingTrivia())
.WithTrailingTrivia(currentFieldDeclaration.GetTrailingTrivia())
.WithAdditionalAnnotations(Formatter.Annotation);
var updatedRoot = declarationRoot.ReplaceNode(currentFieldDeclaration, partialProperty);
return solution.WithDocumentSyntaxRoot(declarationDocument.Id, updatedRoot);
}
private static SyntaxList<AttributeListSyntax> RewriteHookAttributeStrings(
SyntaxList<AttributeListSyntax> attributeLists,
string fieldName,
string backingFieldName,
HashSet<string> getHookAttributeNames,
HashSet<string> setHookAttributeNames)
{
return SyntaxFactory.List(attributeLists.Select(attributeList => attributeList.ReplaceNodes(
attributeList.Attributes,
(_, attribute) =>
{
var attributeName = attribute.Name.ToString();
var isGetHook = getHookAttributeNames.Contains(attributeName);
if (!isGetHook && !setHookAttributeNames.Contains(attributeName))
return attribute;
var arguments = attribute.ArgumentList?.Arguments;
if (arguments is not { Count: > 0 }
|| arguments.Value[0].Expression is not LiteralExpressionSyntax literal
|| !literal.IsKind(SyntaxKind.StringLiteralExpression))
return attribute;
var rewrittenCode = literal.Token.ValueText;
var qualifiedFieldPattern = $@"(?:(?:\b@?[A-Za-z_][A-Za-z0-9_]*\s*\.\s*)*)(?:Current|this)\s*\.\s*{Regex.Escape(fieldName)}\b";
rewrittenCode = Regex.Replace(rewrittenCode, qualifiedFieldPattern, backingFieldName);
if (isGetHook || fieldName != "value")
{
rewrittenCode = Regex.Replace(rewrittenCode, $@"\b{Regex.Escape(fieldName)}\b", backingFieldName);
}
var rewrittenLiteral = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(rewrittenCode));
return attribute.ReplaceNode(literal, rewrittenLiteral);
})));
}
private static SyntaxList<AttributeListSyntax> PreparePartialPropertyAttributes(
SyntaxList<AttributeListSyntax> attributeLists,
string fieldName,
string backingFieldName,
HashSet<string> profileAttributeNames,
HashSet<string> getHookAttributeNames,
HashSet<string> setHookAttributeNames)
{
var rewrittenLists = RewriteHookAttributeStrings(attributeLists, fieldName, backingFieldName, getHookAttributeNames, setHookAttributeNames);
var result = new List<AttributeListSyntax>();
foreach (var attributeList in rewrittenLists)
{
var propertyAttributes = attributeList.Attributes.Where(attribute => profileAttributeNames.Contains(attribute.Name.ToString())).ToArray();
var backingFieldAttributes = attributeList.Attributes.Where(attribute => !profileAttributeNames.Contains(attribute.Name.ToString())).ToArray();
if (propertyAttributes.Length > 0)
{
result.Add(attributeList
.WithTarget(null)
.WithAttributes(SyntaxFactory.SeparatedList(propertyAttributes)));
}
if (backingFieldAttributes.Length > 0)
{
result.Add(attributeList
.WithTarget(SyntaxFactory.AttributeTargetSpecifier(SyntaxFactory.Token(SyntaxKind.FieldKeyword))
.WithColonToken(SyntaxFactory.Token(SyntaxKind.ColonToken)))
.WithAttributes(SyntaxFactory.SeparatedList(backingFieldAttributes))
.WithLeadingTrivia(propertyAttributes.Length == 0 ? attributeList.GetLeadingTrivia() : default));
}
}
return SyntaxFactory.List(result);
}
}