Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2,673 changes: 1,675 additions & 998 deletions .editorconfig

Large diffs are not rendered by default.

16 changes: 12 additions & 4 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All"/>
</ItemGroup>

<!-- Alias Lock to the dedicated System.Threading.Lock on .NET 9+ (faster EnterScope fast path),
and to a plain object elsewhere (the lock statement falls back to Monitor). -->
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net9.0'))">
<Using Include="System.Threading.Lock" Alias="Lock"/>
</ItemGroup>
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net9.0'))">
<Using Include="System.Object" Alias="Lock"/>
</ItemGroup>

<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
Expand All @@ -112,12 +121,11 @@

<ItemGroup>
<PackageReference Include="MinVer" PrivateAssets="all"/>
<PackageReference Include="stylecop.analyzers" PrivateAssets="all"/>
<PackageReference Include="StyleSharp.Analyzers" PrivateAssets="all"/>
<PackageReference Include="PerformanceSharp.Analyzers" PrivateAssets="all"/>
<PackageReference Include="SecuritySharp.Analyzers" PrivateAssets="all"/>
<PackageReference Include="Roslynator.Analyzers"/>
<PackageReference Include="SonarAnalyzer.CSharp"/>
<PackageReference Include="Blazor.Common.Analyzers"/>
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)stylecop.json" Link="stylecop.json"/>
</ItemGroup>
</Project>
59 changes: 38 additions & 21 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,65 @@

<!-- MAUI version varies by target framework -->
<PropertyGroup>
<MauiVersion Condition="$(TargetFramework.StartsWith('net10'))">10.0.41</MauiVersion>
<MauiVersion Condition="$(TargetFramework.StartsWith('net10'))">10.0.90</MauiVersion>
<MauiVersion Condition="$(TargetFramework.StartsWith('net9'))">9.0.120</MauiVersion>
</PropertyGroup>

<PropertyGroup>
<!-- StyleSharp.Analyzers, PerformanceSharp.Analyzers and SecuritySharp.Analyzers ship from the
same release pipeline and always share a version. -->
<RoslynCommonAnalyzersVersion>3.39.2</RoslynCommonAnalyzersVersion>
</PropertyGroup>

<ItemGroup>
<!-- Testing Framework -->
<PackageVersion Include="NSubstitute" Version="5.3.0"/>
<PackageVersion Include="TUnit" Version="1.49.0"/>
<PackageVersion Include="Verify.TUnit" Version="31.19.0"/>
<PackageVersion Include="NSubstitute" Version="6.0.0"/>
<PackageVersion Include="TUnit" Version="1.61.38"/>
<PackageVersion Include="Verify.TUnit" Version="31.27.0"/>
<PackageVersion Include="Verify.SourceGenerators" Version="2.5.0"/>

<!--
Roslyn / Code Analysis — Microsoft.CodeAnalysis.CSharp(.Workspaces) are
deliberately pinned to 4.14.0 to match the minimum Roslyn version the
source generator targets (Visual Studio 2022's bundled Roslyn, our
baseline). Do NOT update these independently; they must stay aligned with
the Roslyn SDK the generator is compiled against, or consumers on older
VS 2022 builds will fail to load the generator.
Roslyn / Code Analysis.

The shipping generator and analyzer projects compile against Roslyn 4.8.0
(VersionOverride in their own .csproj), the version that ships with the
.NET 8 SDK (8.0.1xx) and Visual Studio 2022 17.8. A compiler refuses to
load an analyzer built against a NEWER Roslyn than the one running, so
4.8.0 is the floor for which consumers can use the package at all — do NOT
raise it without deciding to drop those consumers.

The version below is the one the test and benchmark projects compile and
run against; it must stay at or above what their own tooling requires
(Basic.Reference.Assemblies needs >= 4.11, BenchmarkDotNet needs >= 4.14).
Running the generator on a newer Roslyn than it was built against is the
normal, supported direction.

Microsoft.CodeAnalysis.Analyzers is a build-time-only analyzer package
(the RS#### authoring rules) with no impact on the end-user-facing Roslyn
version, so it tracks the latest stable release independently.
-->
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0"/>
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="5.6.0"/>
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0"/>
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.14.0"/>

<!-- Test Infrastructure -->
<PackageVersion Include="Basic.Reference.Assemblies.Net100" Version="1.8.8"/>
<PackageVersion Include="Basic.Reference.Assemblies.Net90" Version="1.8.8"/>
<PackageVersion Include="Basic.Reference.Assemblies.Net80" Version="1.8.8"/>
<PackageVersion Include="Basic.Reference.Assemblies.Net100" Version="1.8.10"/>
<PackageVersion Include="Basic.Reference.Assemblies.Net90" Version="1.8.10"/>
<PackageVersion Include="Basic.Reference.Assemblies.Net80" Version="1.8.10"/>

<!-- Dependencies -->
<PackageVersion Include="Splat" Version="19.4.1"/>
<PackageVersion Include="System.Reactive" Version="6.1.0"/>
<PackageVersion Include="Splat" Version="20.2.0"/>
<PackageVersion Include="ReactiveUI.Primitives" Version="7.1.0"/>
<PackageVersion Include="System.Reactive" Version="7.0.0"/>

<!-- Build Tools -->
<PackageVersion Include="MinVer" Version="7.0.0"/>
<PackageVersion Include="stylecop.analyzers" Version="1.2.0-beta.556"/>
<PackageVersion Include="StyleSharp.Analyzers" Version="$(RoslynCommonAnalyzersVersion)"/>
<PackageVersion Include="PerformanceSharp.Analyzers" Version="$(RoslynCommonAnalyzersVersion)"/>
<PackageVersion Include="SecuritySharp.Analyzers" Version="$(RoslynCommonAnalyzersVersion)"/>
<PackageVersion Include="Roslynator.Analyzers" Version="4.15.0"/>
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.300"/>
<PackageVersion Include="SonarAnalyzer.CSharp" Version="10.27.0.140913"/>
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.301"/>
<PackageVersion Include="SonarAnalyzer.CSharp" Version="10.30.0.144632"/>
<PackageVersion Include="Blazor.Common.Analyzers" Version="2.1.0"/>

<!-- MAUI -->
Expand All @@ -58,7 +75,7 @@
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8"/>

<!-- Test App Dependencies-->
<PackageVersion Include="ReactiveUI" Version="23.2.28"/>
<PackageVersion Include="ReactiveUI.SourceGenerators" Version="3.0.0"/>
<PackageVersion Include="ReactiveUI" Version="24.0.0"/>
<PackageVersion Include="ReactiveUI.SourceGenerators" Version="3.1.0"/>
</ItemGroup>
</Project>
63 changes: 14 additions & 49 deletions src/ReactiveUI.Binding.Analyzer/Analyzers/AnalyzerHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,17 @@

namespace ReactiveUI.Binding.Analyzer.Analyzers;

/// <summary>
/// Shared helper methods for analyzers. No LINQ, manual loops.
/// </summary>
/// <summary>Shared helper methods for analyzers. No LINQ, manual loops.</summary>
internal static class AnalyzerHelpers
{
/// <summary>
/// Checks if a method symbol belongs to our generated extension class.
/// </summary>
/// <summary>Checks if a method symbol belongs to our generated extension class.</summary>
/// <param name="methodSymbol">The method symbol to check.</param>
/// <returns>true if the method is from our generated extension class.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsBindingExtensionMethod(IMethodSymbol methodSymbol)
{
var containingType = methodSymbol.ContainingType;
if (containingType == null)
if (containingType is null)
{
return false;
}
Expand All @@ -32,18 +28,14 @@ internal static bool IsBindingExtensionMethod(IMethodSymbol methodSymbol)
or SourceGenerators.Constants.StubExtensionClassName;
}

/// <summary>
/// Checks if an expression is an inline lambda (not a variable reference or method call).
/// </summary>
/// <summary>Checks if an expression is an inline lambda (not a variable reference or method call).</summary>
/// <param name="expression">The expression to check.</param>
/// <returns>true if the expression is an inline lambda.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsInlineLambda(ExpressionSyntax expression) =>
expression is SimpleLambdaExpressionSyntax or ParenthesizedLambdaExpressionSyntax;

/// <summary>
/// Checks if a type supports before-change notifications based on its notification mechanism.
/// </summary>
/// <summary>Checks if a type supports before-change notifications based on its notification mechanism.</summary>
/// <param name="typeSymbol">The type to check.</param>
/// <param name="compilation">The current compilation for type resolution.</param>
/// <param name="mechanism">Output: the name of the detected mechanism.</param>
Expand Down Expand Up @@ -117,15 +109,7 @@ internal static bool HasBeforeChangeSupport(
/// <param name="methodSymbol">The method symbol to extract from.</param>
/// <returns>The first type argument as <see cref="INamedTypeSymbol"/>, or null.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static INamedTypeSymbol? ExtractFirstTypeArgument(IMethodSymbol methodSymbol)
{
if (methodSymbol.TypeArguments.Length == 0)
{
return null;
}

return methodSymbol.TypeArguments[0] as INamedTypeSymbol;
}
internal static INamedTypeSymbol? ExtractFirstTypeArgument(IMethodSymbol methodSymbol) => methodSymbol.TypeArguments.IsEmpty ? null : methodSymbol.TypeArguments[0] as INamedTypeSymbol;

/// <summary>
/// Determines whether a method's first type argument lacks any observable notification mechanism.
Expand All @@ -141,12 +125,7 @@ internal static bool LacksObservableMechanism(
out INamedTypeSymbol? sourceType)
{
sourceType = ExtractFirstTypeArgument(methodSymbol);
if (sourceType == null)
{
return false;
}

return !TypeAnalyzer.HasObservableMechanism(sourceType, compilation);
return sourceType is null ? false : !TypeAnalyzer.HasObservableMechanism(sourceType, compilation);
}

/// <summary>
Expand All @@ -166,12 +145,7 @@ internal static bool LacksBeforeChangeSupport(
{
mechanism = string.Empty;
receiverType = ExtractFirstTypeArgument(methodSymbol);
if (receiverType == null)
{
return false;
}

return !HasBeforeChangeSupport(receiverType, compilation, out mechanism);
return receiverType is null ? false : !HasBeforeChangeSupport(receiverType, compilation, out mechanism);
}

/// <summary>
Expand All @@ -190,24 +164,17 @@ internal static bool ImplementsDataErrorInfo(
out INamedTypeSymbol? sourceType)
{
sourceType = ExtractFirstTypeArgument(methodSymbol);
if (sourceType == null)
if (sourceType is null)
{
return false;
}

var dataErrorInfo =
compilation.GetTypeByMetadataName(SourceGenerators.Constants.INotifyDataErrorInfoMetadataName);
if (dataErrorInfo == null)
{
return false;
}

return ImplementsInterface(sourceType, dataErrorInfo);
return dataErrorInfo is null ? false : ImplementsInterface(sourceType, dataErrorInfo);
}

/// <summary>
/// Determines whether a type implements a specific interface.
/// </summary>
/// <summary>Determines whether a type implements a specific interface.</summary>
/// <param name="typeSymbol">The type symbol to check.</param>
/// <param name="interfaceSymbol">The interface symbol to look for.</param>
/// <returns><c>true</c> if the type implements the specified interface; otherwise, <c>false</c>.</returns>
Expand All @@ -225,16 +192,14 @@ internal static bool ImplementsInterface(INamedTypeSymbol typeSymbol, INamedType
return false;
}

/// <summary>
/// Determines whether a type inherits from a specific base type.
/// </summary>
/// <summary>Determines whether a type inherits from a specific base type.</summary>
/// <param name="typeSymbol">The type symbol to check.</param>
/// <param name="baseTypeSymbol">The base type symbol to look for in the inheritance hierarchy.</param>
/// <returns><c>true</c> if the type inherits from the specified base type; otherwise, <c>false</c>.</returns>
internal static bool InheritsFrom(INamedTypeSymbol typeSymbol, INamedTypeSymbol baseTypeSymbol)
{
var current = typeSymbol.BaseType;
while (current != null)
while (current is not null)
{
if (SymbolEqualityComparer.Default.Equals(current, baseTypeSymbol))
{
Expand Down Expand Up @@ -265,7 +230,7 @@ private static bool Matches(
bool byInterface)
{
var target = compilation.GetTypeByMetadataName(metadataName);
if (target == null)
if (target is null)
{
return false;
}
Expand Down
Loading
Loading