Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
11d15c9
Add blank 'Roslyn4110' project
Sergio0694 Oct 23, 2024
e27359f
Fix naming conventions for all Roslyn projects
Sergio0694 Oct 23, 2024
a67a31d
Add 'global.json' file
Sergio0694 Oct 23, 2024
9c9061d
Update .targets for Roslyn setup
Sergio0694 Oct 23, 2024
f4fdb27
Pack new source generator in MVVM Toolkit
Sergio0694 Oct 23, 2024
c8e794b
Add logic to match partial properties
Sergio0694 Oct 23, 2024
76264f7
Update the generator to also work on properties
Sergio0694 Oct 23, 2024
3df53f3
Add 'RequiresCSharpLanguageVersionPreviewAnalyzer'
Sergio0694 Oct 23, 2024
fff5f91
Suppress warnings about removed rules
Sergio0694 Oct 23, 2024
41dc834
Add blank test project for new generator
Sergio0694 Oct 23, 2024
34a7c6f
Update '[ObservableProperty]' attribute
Sergio0694 Oct 23, 2024
bbd59ec
Add unit tests for new analyzer
Sergio0694 Oct 23, 2024
f970ba6
Move forwarded attributes gathering to helper
Sergio0694 Oct 24, 2024
e5b2802
Gather accessibility information from nodes
Sergio0694 Oct 24, 2024
17263fe
Generalizing the generated accessibility modifiers
Sergio0694 Oct 24, 2024
5f2ffcf
Don't emit an error for collisions for properties
Sergio0694 Oct 24, 2024
46d03e8
Update generation to account for properties
Sergio0694 Oct 24, 2024
52ad254
Fix a generator crash when used on properties
Sergio0694 Oct 24, 2024
8ca2f3c
Omit implicit accessibility modifiers
Sergio0694 Oct 24, 2024
db221ed
Update the targets of additional attributes
Sergio0694 Oct 24, 2024
f9f7771
Fix handling notify data error info
Sergio0694 Oct 24, 2024
5d37b73
Fix nullability for generated partial properties
Sergio0694 Oct 24, 2024
aabcd15
Add initial codegen tests for partial properties
Sergio0694 Oct 24, 2024
62c06f1
Add 'UseObservablePropertyOnPartialPropertyAnalyzer'
Sergio0694 Oct 24, 2024
6342b83
Add unit tests for new analyzer
Sergio0694 Oct 24, 2024
efa90a3
Add 'InvalidPropertyLevelObservablePropertyAttributeAnalyzer'
Sergio0694 Oct 24, 2024
767c05b
Add 'UnsupportedRoslynVersionForPartialPropertyAnalyzer'
Sergio0694 Oct 24, 2024
eea59fd
Fix handling of 'private protected' accessors
Sergio0694 Oct 24, 2024
162bab5
Add unit tests for invalid property declarations
Sergio0694 Oct 24, 2024
5e02b9a
Add 'UsePartialPropertyForObservablePropertyCodeFixer'
Sergio0694 Oct 25, 2024
9ba8f27
Add new helper for testing code fixers
Sergio0694 Oct 25, 2024
b98a658
Add unit test for new code fixer
Sergio0694 Oct 25, 2024
8cdebe0
Fix attributes handling, add unit tests
Sergio0694 Oct 25, 2024
e6a5c09
Add unit tests for comments in code fixer
Sergio0694 Oct 25, 2024
00ebe42
Support updating field references as well
Sergio0694 Oct 25, 2024
3453824
Improve messages in diagnostics when emitted on properties
Sergio0694 Oct 25, 2024
7b402b6
Add workaround for older Roslyn versions
Sergio0694 Oct 25, 2024
ca5d9dd
Fix a unit test on .NET Framework
Sergio0694 Oct 25, 2024
648788c
Fix last remaining unit test, and test regex
Sergio0694 Oct 25, 2024
9c9ff44
Set version.json to 8.4.0
Sergio0694 Oct 25, 2024
f75edab
Don't suggest partial properties for static fields
Sergio0694 Oct 25, 2024
0a81f27
Support field initializers in code fixer
Sergio0694 Oct 25, 2024
a7840b2
Don't run analyzers on generated code
Sergio0694 Oct 26, 2024
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
Prev Previous commit
Next Next commit
Move forwarded attributes gathering to helper
  • Loading branch information
Sergio0694 committed Oct 24, 2024
commit f970ba6c0b72bb44d06e666b578a4386d3bb806f
Original file line number Diff line number Diff line change
Expand Up @@ -339,71 +339,15 @@ public static bool TryGetInfo(

token.ThrowIfCancellationRequested();

// Gather explicit forwarded attributes info
foreach (AttributeListSyntax attributeList in memberSyntax.AttributeLists)
{
// For properties, we never need to forward any attributes with explicit targets either, because
// they can already "just work" when used with 'field'. As for 'get' and 'set', they can just be
// added directly to the partial declarations of the property accessors.
if (memberSyntax.IsKind(SyntaxKind.PropertyDeclaration))
{
continue;
}

// Only look for attribute lists explicitly targeting the (generated) property or one of its accessors. Roslyn will
// normally emit a CS0657 warning (invalid target), but that is automatically suppressed by a dedicated diagnostic
// suppressor that recognizes uses of this target specifically to support [ObservableProperty].
if (attributeList.Target?.Identifier is not SyntaxToken(SyntaxKind.PropertyKeyword or SyntaxKind.GetKeyword or SyntaxKind.SetKeyword) targetIdentifier)
{
continue;
}

token.ThrowIfCancellationRequested();

foreach (AttributeSyntax attribute in attributeList.Attributes)
{
// Roslyn ignores attributes in an attribute list with an invalid target, so we can't get the AttributeData as usual.
// To reconstruct all necessary attribute info to generate the serialized model, we use the following steps:
// - We try to get the attribute symbol from the semantic model, for the current attribute syntax. In case this is not
// available (in theory it shouldn't, but it can be), we try to get it from the candidate symbols list for the node.
// If there are no candidates or more than one, we just issue a diagnostic and stop processing the current attribute.
// The returned symbols might be method symbols (constructor attribute) so in that case we can get the declaring type.
// - We then go over each attribute argument expression and get the operation for it. This will still be available even
// though the rest of the attribute is not validated nor bound at all. From the operation we can still retrieve all
// constant values to build the AttributeInfo model. After all, attributes only support constant values, typeof(T)
// expressions, or arrays of either these two types, or of other arrays with the same rules, recursively.
// - From the syntax, we can also determine the identifier names for named attribute arguments, if any.
// There is no need to validate anything here: the attribute will be forwarded as is, and then Roslyn will validate on the
// generated property. Users will get the same validation they'd have had directly over the field. The only drawback is the
// lack of IntelliSense when constructing attributes over the field, but this is the best we can do from this end anyway.
if (!semanticModel.GetSymbolInfo(attribute, token).TryGetAttributeTypeSymbol(out INamedTypeSymbol? attributeTypeSymbol))
{
builder.Add(
InvalidPropertyTargetedAttributeOnObservablePropertyField,
attribute,
memberSymbol,
attribute.Name);

continue;
}

IEnumerable<AttributeArgumentSyntax> attributeArguments = attribute.ArgumentList?.Arguments ?? Enumerable.Empty<AttributeArgumentSyntax>();

// Try to extract the forwarded attribute
if (!AttributeInfo.TryCreate(attributeTypeSymbol, semanticModel, attributeArguments, targetIdentifier.Kind(), token, out AttributeInfo? attributeInfo))
{
builder.Add(
InvalidPropertyTargetedAttributeExpressionOnObservablePropertyField,
attribute,
memberSymbol,
attribute.Name);

continue;
}

forwardedAttributes.Add(attributeInfo);
}
}
// Also gather any forwarded attributes on the annotated member, if it is a field.
// This method will not do anything for properties, as those don't support this.
GatherLegacyForwardedAttributes(
memberSyntax,
memberSymbol,
semanticModel,
in forwardedAttributes,
in builder,
token);

token.ThrowIfCancellationRequested();

Expand Down Expand Up @@ -933,6 +877,90 @@ private static void GetNullabilityInfo(
semanticModel.Compilation.HasAccessibleTypeWithMetadataName("System.Diagnostics.CodeAnalysis.MemberNotNullAttribute");
}

/// <summary>
/// Gathers all forwarded attributes from the given member syntax.
/// </summary>
/// <param name="memberSyntax">The <see cref="MemberDeclarationSyntax"/> instance to process.</param>
/// <param name="memberSymbol">The input <see cref="ISymbol"/> instance to process.</param>
/// <param name="semanticModel">The <see cref="SemanticModel"/> instance for the current run.</param>
/// <param name="forwardedAttributes">The collection of forwarded attributes to add new ones to.</param>
/// <param name="diagnostics">The current collection of gathered diagnostics.</param>
/// <param name="token">The cancellation token for the current operation.</param>
private static void GatherLegacyForwardedAttributes(
MemberDeclarationSyntax memberSyntax,
ISymbol memberSymbol,
SemanticModel semanticModel,
in ImmutableArrayBuilder<AttributeInfo> forwardedAttributes,
in ImmutableArrayBuilder<DiagnosticInfo> diagnostics,
CancellationToken token)
{
// For properties, we never need to forward any attributes with explicit targets either, because
// they can already "just work" when used with 'field'. As for 'get' and 'set', they can just be
// added directly to the partial declarations of the property accessors.
if (memberSyntax.IsKind(SyntaxKind.PropertyDeclaration))
{
return;
}

// Gather explicit forwarded attributes info
foreach (AttributeListSyntax attributeList in memberSyntax.AttributeLists)
{
// Only look for attribute lists explicitly targeting the (generated) property or one of its accessors. Roslyn will
// normally emit a CS0657 warning (invalid target), but that is automatically suppressed by a dedicated diagnostic
// suppressor that recognizes uses of this target specifically to support [ObservableProperty].
if (attributeList.Target?.Identifier is not SyntaxToken(SyntaxKind.PropertyKeyword or SyntaxKind.GetKeyword or SyntaxKind.SetKeyword) targetIdentifier)
{
continue;
}

token.ThrowIfCancellationRequested();

foreach (AttributeSyntax attribute in attributeList.Attributes)
{
// Roslyn ignores attributes in an attribute list with an invalid target, so we can't get the AttributeData as usual.
// To reconstruct all necessary attribute info to generate the serialized model, we use the following steps:
// - We try to get the attribute symbol from the semantic model, for the current attribute syntax. In case this is not
// available (in theory it shouldn't, but it can be), we try to get it from the candidate symbols list for the node.
// If there are no candidates or more than one, we just issue a diagnostic and stop processing the current attribute.
// The returned symbols might be method symbols (constructor attribute) so in that case we can get the declaring type.
// - We then go over each attribute argument expression and get the operation for it. This will still be available even
// though the rest of the attribute is not validated nor bound at all. From the operation we can still retrieve all
// constant values to build the AttributeInfo model. After all, attributes only support constant values, typeof(T)
// expressions, or arrays of either these two types, or of other arrays with the same rules, recursively.
// - From the syntax, we can also determine the identifier names for named attribute arguments, if any.
// There is no need to validate anything here: the attribute will be forwarded as is, and then Roslyn will validate on the
// generated property. Users will get the same validation they'd have had directly over the field. The only drawback is the
// lack of IntelliSense when constructing attributes over the field, but this is the best we can do from this end anyway.
if (!semanticModel.GetSymbolInfo(attribute, token).TryGetAttributeTypeSymbol(out INamedTypeSymbol? attributeTypeSymbol))
{
diagnostics.Add(
InvalidPropertyTargetedAttributeOnObservablePropertyField,
attribute,
memberSymbol,
attribute.Name);

continue;
}

IEnumerable<AttributeArgumentSyntax> attributeArguments = attribute.ArgumentList?.Arguments ?? Enumerable.Empty<AttributeArgumentSyntax>();

// Try to extract the forwarded attribute
if (!AttributeInfo.TryCreate(attributeTypeSymbol, semanticModel, attributeArguments, targetIdentifier.Kind(), token, out AttributeInfo? attributeInfo))
{
diagnostics.Add(
InvalidPropertyTargetedAttributeExpressionOnObservablePropertyField,
attribute,
memberSymbol,
attribute.Name);

continue;
}

forwardedAttributes.Add(attributeInfo);
}
}
}

/// <summary>
/// Tries to get the accessibility of the property and accessors, if possible.
/// </summary>
Expand Down