Skip to content

Commit f98b5b5

Browse files
authored
Update Code Fix to report more accurately (#178)
1 parent 8c53514 commit f98b5b5

File tree

17 files changed

+947
-4
lines changed

17 files changed

+947
-4
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using Microsoft.CodeAnalysis;
7+
8+
namespace ReactiveUI.SourceGenerators.Extensions;
9+
10+
internal static class FieldSyntaxExtensions
11+
{
12+
/// <summary>
13+
/// Validates the containing type for a given field being annotated.
14+
/// </summary>
15+
/// <param name="fieldSymbol">The input <see cref="IFieldSymbol"/> instance to process.</param>
16+
/// <returns>Whether or not the containing type for <paramref name="fieldSymbol"/> is valid.</returns>
17+
internal static bool IsTargetTypeValid(this IFieldSymbol fieldSymbol)
18+
{
19+
var isObservableObject = fieldSymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("ReactiveUI.ReactiveObject");
20+
var isIObservableObject = fieldSymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("ReactiveUI.IReactiveObject");
21+
var hasObservableObjectAttribute = fieldSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedMetadataName("ReactiveUI.SourceGenerators.ReactiveObjectAttribute");
22+
23+
return isIObservableObject || isObservableObject || hasObservableObjectAttribute;
24+
}
25+
26+
/// <summary>
27+
/// Validates the containing type for a given field being annotated.
28+
/// </summary>
29+
/// <param name="propertySymbol">The input <see cref="IFieldSymbol"/> instance to process.</param>
30+
/// <returns>Whether or not the containing type for <paramref name="propertySymbol"/> is valid.</returns>
31+
internal static bool IsTargetTypeValid(this IPropertySymbol propertySymbol)
32+
{
33+
var isObservableObject = propertySymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("ReactiveUI.ReactiveObject");
34+
var isIObservableObject = propertySymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("ReactiveUI.IReactiveObject");
35+
var hasObservableObjectAttribute = propertySymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedMetadataName("ReactiveUI.SourceGenerators.ReactiveObjectAttribute");
36+
37+
return isIObservableObject || isObservableObject || hasObservableObjectAttribute;
38+
}
39+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using Microsoft.CodeAnalysis;
7+
8+
namespace ReactiveUI.SourceGenerators.Extensions;
9+
10+
/// <summary>
11+
/// Extension methods for the <see cref="ISymbol"/> type.
12+
/// </summary>
13+
internal static class ISymbolExtensions
14+
{
15+
/// <summary>
16+
/// Checks whether or not a given symbol has an attribute with the specified fully qualified metadata name.
17+
/// </summary>
18+
/// <param name="symbol">The input <see cref="ISymbol"/> instance to check.</param>
19+
/// <param name="name">The attribute name to look for.</param>
20+
/// <returns>Whether or not <paramref name="symbol"/> has an attribute with the specified name.</returns>
21+
public static bool HasAttributeWithFullyQualifiedMetadataName(this ISymbol symbol, string name)
22+
{
23+
foreach (var attribute in symbol.GetAttributes())
24+
{
25+
if (attribute.AttributeClass?.HasFullyQualifiedMetadataName(name) == true)
26+
{
27+
return true;
28+
}
29+
}
30+
31+
return false;
32+
}
33+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using System;
7+
using Microsoft.CodeAnalysis;
8+
using ReactiveUI.SourceGenerators.Helpers;
9+
10+
namespace ReactiveUI.SourceGenerators.Extensions;
11+
12+
/// <summary>
13+
/// Extension methods for the <see cref="ITypeSymbol"/> type.
14+
/// </summary>
15+
internal static class ITypeSymbolExtensions
16+
{
17+
/// <summary>
18+
/// Checks whether or not a given <see cref="ITypeSymbol"/> inherits from a specified type.
19+
/// </summary>
20+
/// <param name="typeSymbol">The target <see cref="ITypeSymbol"/> instance to check.</param>
21+
/// <param name="name">The full name of the type to check for inheritance.</param>
22+
/// <returns>Whether or not <paramref name="typeSymbol"/> inherits from <paramref name="name"/>.</returns>
23+
public static bool InheritsFromFullyQualifiedMetadataName(this ITypeSymbol typeSymbol, string name)
24+
{
25+
var baseType = typeSymbol.BaseType;
26+
27+
while (baseType is not null)
28+
{
29+
if (baseType.HasFullyQualifiedMetadataName(name))
30+
{
31+
return true;
32+
}
33+
34+
baseType = baseType.BaseType;
35+
}
36+
37+
return false;
38+
}
39+
40+
/// <summary>
41+
/// Checks whether or not a given <see cref="ITypeSymbol"/> has or inherits a specified attribute.
42+
/// </summary>
43+
/// <param name="typeSymbol">The target <see cref="ITypeSymbol"/> instance to check.</param>
44+
/// <param name="name">The name of the attribute to look for.</param>
45+
/// <returns>Whether or not <paramref name="typeSymbol"/> has an attribute with the specified type name.</returns>
46+
public static bool HasOrInheritsAttributeWithFullyQualifiedMetadataName(this ITypeSymbol typeSymbol, string name)
47+
{
48+
for (var currentType = typeSymbol; currentType is not null; currentType = currentType.BaseType)
49+
{
50+
if (currentType.HasAttributeWithFullyQualifiedMetadataName(name))
51+
{
52+
return true;
53+
}
54+
}
55+
56+
return false;
57+
}
58+
59+
/// <summary>
60+
/// Checks whether or not a given type symbol has a specified fully qualified metadata name.
61+
/// </summary>
62+
/// <param name="symbol">The input <see cref="ITypeSymbol"/> instance to check.</param>
63+
/// <param name="name">The full name to check.</param>
64+
/// <returns>Whether <paramref name="symbol"/> has a full name equals to <paramref name="name"/>.</returns>
65+
public static bool HasFullyQualifiedMetadataName(this ITypeSymbol symbol, string name)
66+
{
67+
using var builder = ImmutableArrayBuilder<char>.Rent();
68+
69+
symbol.AppendFullyQualifiedMetadataName(builder);
70+
71+
return builder.WrittenSpan.StartsWith(name.AsSpan());
72+
}
73+
74+
/// <summary>
75+
/// Appends the fully qualified metadata name for a given symbol to a target builder.
76+
/// </summary>
77+
/// <param name="symbol">The input <see cref="ITypeSymbol"/> instance.</param>
78+
/// <param name="builder">The target <see cref="ImmutableArrayBuilder{T}"/> instance.</param>
79+
private static void AppendFullyQualifiedMetadataName(this ITypeSymbol symbol, ImmutableArrayBuilder<char> builder)
80+
{
81+
static void BuildFrom(ISymbol? symbol, ImmutableArrayBuilder<char> builder)
82+
{
83+
switch (symbol)
84+
{
85+
// Namespaces that are nested also append a leading '.'
86+
case INamespaceSymbol { ContainingNamespace.IsGlobalNamespace: false }:
87+
BuildFrom(symbol.ContainingNamespace, builder);
88+
builder.Add('.');
89+
builder.AddRange(symbol.MetadataName.AsSpan());
90+
break;
91+
92+
// Other namespaces (ie. the one right before global) skip the leading '.'
93+
case INamespaceSymbol { IsGlobalNamespace: false }:
94+
builder.AddRange(symbol.MetadataName.AsSpan());
95+
break;
96+
97+
// Types with no namespace just have their metadata name directly written
98+
case ITypeSymbol { ContainingSymbol: INamespaceSymbol { IsGlobalNamespace: true } }:
99+
builder.AddRange(symbol.MetadataName.AsSpan());
100+
break;
101+
102+
// Types with a containing non-global namespace also append a leading '.'
103+
case ITypeSymbol { ContainingSymbol: INamespaceSymbol namespaceSymbol }:
104+
BuildFrom(namespaceSymbol, builder);
105+
builder.Add('.');
106+
builder.AddRange(symbol.MetadataName.AsSpan());
107+
break;
108+
109+
// Nested types append a leading '+'
110+
case ITypeSymbol { ContainingSymbol: ITypeSymbol typeSymbol }:
111+
BuildFrom(typeSymbol, builder);
112+
builder.Add('+');
113+
builder.AddRange(symbol.MetadataName.AsSpan());
114+
break;
115+
default:
116+
break;
117+
}
118+
}
119+
120+
BuildFrom(symbol, builder);
121+
}
122+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// <auto-generated/>
2+
#pragma warning disable
3+
#nullable enable annotations
4+
5+
// Licensed to the .NET Foundation under one or more agreements.
6+
// The .NET Foundation licenses this file to you under the MIT license.
7+
8+
namespace System.Diagnostics.CodeAnalysis
9+
{
10+
/// <summary>
11+
/// Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.
12+
/// </summary>
13+
[global::System.AttributeUsage(global::System.AttributeTargets.Parameter, Inherited = false)]
14+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
15+
internal sealed class NotNullWhenAttribute : global::System.Attribute
16+
{
17+
/// <summary>
18+
/// Initializes the attribute with the specified return value condition.
19+
/// </summary>
20+
/// <param name="returnValue">The return value condition. If the method returns this value, the associated parameter will not be null.</param>
21+
public NotNullWhenAttribute(bool returnValue)
22+
{
23+
ReturnValue = returnValue;
24+
}
25+
26+
/// <summary>Gets the return value condition.</summary>
27+
public bool ReturnValue { get; }
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// <auto-generated/>
2+
#pragma warning disable
3+
#nullable enable annotations
4+
5+
// Licensed to the .NET Foundation under one or more agreements.
6+
// The .NET Foundation licenses this file to you under the MIT license.
7+
8+
namespace System.Diagnostics.CodeAnalysis
9+
{
10+
/// <summary>
11+
/// Used to indicate a byref escapes and is not scoped.
12+
/// </summary>
13+
/// <remarks>
14+
/// <para>
15+
/// There are several cases where the C# compiler treats a <see langword="ref"/> as implicitly
16+
/// <see langword="scoped"/> - where the compiler does not allow the <see langword="ref"/> to escape the method.
17+
/// </para>
18+
/// <para>
19+
/// For example:
20+
/// <list type="number">
21+
/// <item><see langword="this"/> for <see langword="struct"/> instance methods.</item>
22+
/// <item><see langword="ref"/> parameters that refer to <see langword="ref"/> <see langword="struct"/> types.</item>
23+
/// <item><see langword="out"/> parameters.</item>
24+
/// </list>
25+
/// </para>
26+
/// <para>
27+
/// This attribute is used in those instances where the <see langword="ref"/> should be allowed to escape.
28+
/// </para>
29+
/// <para>
30+
/// Applying this attribute, in any form, has impact on consumers of the applicable API. It is necessary for
31+
/// API authors to understand the lifetime implications of applying this attribute and how it may impact their users.
32+
/// </para>
33+
/// </remarks>
34+
[global::System.AttributeUsage(
35+
global::System.AttributeTargets.Method |
36+
global::System.AttributeTargets.Property |
37+
global::System.AttributeTargets.Parameter,
38+
AllowMultiple = false,
39+
Inherited = false)]
40+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
41+
internal sealed class UnscopedRefAttribute : global::System.Attribute
42+
{
43+
}
44+
}

0 commit comments

Comments
 (0)