Skip to content
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
@@ -0,0 +1,39 @@
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using Microsoft.CodeAnalysis;

namespace ReactiveUI.SourceGenerators.Extensions;

internal static class FieldSyntaxExtensions
{
/// <summary>
/// Validates the containing type for a given field being annotated.
/// </summary>
/// <param name="fieldSymbol">The input <see cref="IFieldSymbol"/> instance to process.</param>
/// <returns>Whether or not the containing type for <paramref name="fieldSymbol"/> is valid.</returns>
internal static bool IsTargetTypeValid(this IFieldSymbol fieldSymbol)
{
var isObservableObject = fieldSymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("ReactiveUI.ReactiveObject");
var isIObservableObject = fieldSymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("ReactiveUI.IReactiveObject");
var hasObservableObjectAttribute = fieldSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedMetadataName("ReactiveUI.SourceGenerators.ReactiveObjectAttribute");

return isIObservableObject || isObservableObject || hasObservableObjectAttribute;
}

/// <summary>
/// Validates the containing type for a given field being annotated.
/// </summary>
/// <param name="propertySymbol">The input <see cref="IFieldSymbol"/> instance to process.</param>
/// <returns>Whether or not the containing type for <paramref name="propertySymbol"/> is valid.</returns>
internal static bool IsTargetTypeValid(this IPropertySymbol propertySymbol)
{
var isObservableObject = propertySymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("ReactiveUI.ReactiveObject");
var isIObservableObject = propertySymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("ReactiveUI.IReactiveObject");
var hasObservableObjectAttribute = propertySymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedMetadataName("ReactiveUI.SourceGenerators.ReactiveObjectAttribute");

return isIObservableObject || isObservableObject || hasObservableObjectAttribute;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using Microsoft.CodeAnalysis;

namespace ReactiveUI.SourceGenerators.Extensions;

/// <summary>
/// Extension methods for the <see cref="ISymbol"/> type.
/// </summary>
internal static class ISymbolExtensions
{
/// <summary>
/// Checks whether or not a given symbol has an attribute with the specified fully qualified metadata name.
/// </summary>
/// <param name="symbol">The input <see cref="ISymbol"/> instance to check.</param>
/// <param name="name">The attribute name to look for.</param>
/// <returns>Whether or not <paramref name="symbol"/> has an attribute with the specified name.</returns>
public static bool HasAttributeWithFullyQualifiedMetadataName(this ISymbol symbol, string name)
{
foreach (var attribute in symbol.GetAttributes())
{
if (attribute.AttributeClass?.HasFullyQualifiedMetadataName(name) == true)
{
return true;
}
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System;
using Microsoft.CodeAnalysis;
using ReactiveUI.SourceGenerators.Helpers;

namespace ReactiveUI.SourceGenerators.Extensions;

/// <summary>
/// Extension methods for the <see cref="ITypeSymbol"/> type.
/// </summary>
internal static class ITypeSymbolExtensions
{
/// <summary>
/// Checks whether or not a given <see cref="ITypeSymbol"/> inherits from a specified type.
/// </summary>
/// <param name="typeSymbol">The target <see cref="ITypeSymbol"/> instance to check.</param>
/// <param name="name">The full name of the type to check for inheritance.</param>
/// <returns>Whether or not <paramref name="typeSymbol"/> inherits from <paramref name="name"/>.</returns>
public static bool InheritsFromFullyQualifiedMetadataName(this ITypeSymbol typeSymbol, string name)
{
var baseType = typeSymbol.BaseType;

while (baseType is not null)
{
if (baseType.HasFullyQualifiedMetadataName(name))
{
return true;
}

baseType = baseType.BaseType;
}

return false;
}

/// <summary>
/// Checks whether or not a given <see cref="ITypeSymbol"/> has or inherits a specified attribute.
/// </summary>
/// <param name="typeSymbol">The target <see cref="ITypeSymbol"/> instance to check.</param>
/// <param name="name">The name of the attribute to look for.</param>
/// <returns>Whether or not <paramref name="typeSymbol"/> has an attribute with the specified type name.</returns>
public static bool HasOrInheritsAttributeWithFullyQualifiedMetadataName(this ITypeSymbol typeSymbol, string name)
{
for (var currentType = typeSymbol; currentType is not null; currentType = currentType.BaseType)
{
if (currentType.HasAttributeWithFullyQualifiedMetadataName(name))
{
return true;
}
}

return false;
}

/// <summary>
/// Checks whether or not a given type symbol has a specified fully qualified metadata name.
/// </summary>
/// <param name="symbol">The input <see cref="ITypeSymbol"/> instance to check.</param>
/// <param name="name">The full name to check.</param>
/// <returns>Whether <paramref name="symbol"/> has a full name equals to <paramref name="name"/>.</returns>
public static bool HasFullyQualifiedMetadataName(this ITypeSymbol symbol, string name)
{
using var builder = ImmutableArrayBuilder<char>.Rent();

symbol.AppendFullyQualifiedMetadataName(builder);

return builder.WrittenSpan.StartsWith(name.AsSpan());
}

/// <summary>
/// Appends the fully qualified metadata name for a given symbol to a target builder.
/// </summary>
/// <param name="symbol">The input <see cref="ITypeSymbol"/> instance.</param>
/// <param name="builder">The target <see cref="ImmutableArrayBuilder{T}"/> instance.</param>
private static void AppendFullyQualifiedMetadataName(this ITypeSymbol symbol, ImmutableArrayBuilder<char> builder)
{
static void BuildFrom(ISymbol? symbol, ImmutableArrayBuilder<char> builder)
{
switch (symbol)
{
// Namespaces that are nested also append a leading '.'
case INamespaceSymbol { ContainingNamespace.IsGlobalNamespace: false }:
BuildFrom(symbol.ContainingNamespace, builder);
builder.Add('.');
builder.AddRange(symbol.MetadataName.AsSpan());
break;

// Other namespaces (ie. the one right before global) skip the leading '.'
case INamespaceSymbol { IsGlobalNamespace: false }:
builder.AddRange(symbol.MetadataName.AsSpan());
break;

// Types with no namespace just have their metadata name directly written
case ITypeSymbol { ContainingSymbol: INamespaceSymbol { IsGlobalNamespace: true } }:
builder.AddRange(symbol.MetadataName.AsSpan());
break;

// Types with a containing non-global namespace also append a leading '.'
case ITypeSymbol { ContainingSymbol: INamespaceSymbol namespaceSymbol }:
BuildFrom(namespaceSymbol, builder);
builder.Add('.');
builder.AddRange(symbol.MetadataName.AsSpan());
break;

// Nested types append a leading '+'
case ITypeSymbol { ContainingSymbol: ITypeSymbol typeSymbol }:
BuildFrom(typeSymbol, builder);
builder.Add('+');
builder.AddRange(symbol.MetadataName.AsSpan());
break;
default:
break;
}
}

BuildFrom(symbol, builder);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// <auto-generated/>
#pragma warning disable
#nullable enable annotations

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Diagnostics.CodeAnalysis
{
/// <summary>
/// Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.
/// </summary>
[global::System.AttributeUsage(global::System.AttributeTargets.Parameter, Inherited = false)]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal sealed class NotNullWhenAttribute : global::System.Attribute
{
/// <summary>
/// Initializes the attribute with the specified return value condition.
/// </summary>
/// <param name="returnValue">The return value condition. If the method returns this value, the associated parameter will not be null.</param>
public NotNullWhenAttribute(bool returnValue)
{
ReturnValue = returnValue;
}

/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// <auto-generated/>
#pragma warning disable
#nullable enable annotations

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Diagnostics.CodeAnalysis
{
/// <summary>
/// Used to indicate a byref escapes and is not scoped.
/// </summary>
/// <remarks>
/// <para>
/// There are several cases where the C# compiler treats a <see langword="ref"/> as implicitly
/// <see langword="scoped"/> - where the compiler does not allow the <see langword="ref"/> to escape the method.
/// </para>
/// <para>
/// For example:
/// <list type="number">
/// <item><see langword="this"/> for <see langword="struct"/> instance methods.</item>
/// <item><see langword="ref"/> parameters that refer to <see langword="ref"/> <see langword="struct"/> types.</item>
/// <item><see langword="out"/> parameters.</item>
/// </list>
/// </para>
/// <para>
/// This attribute is used in those instances where the <see langword="ref"/> should be allowed to escape.
/// </para>
/// <para>
/// Applying this attribute, in any form, has impact on consumers of the applicable API. It is necessary for
/// API authors to understand the lifetime implications of applying this attribute and how it may impact their users.
/// </para>
/// </remarks>
[global::System.AttributeUsage(
global::System.AttributeTargets.Method |
global::System.AttributeTargets.Property |
global::System.AttributeTargets.Parameter,
AllowMultiple = false,
Inherited = false)]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal sealed class UnscopedRefAttribute : global::System.Attribute
{
}
}
Loading
Loading