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
Expand Up @@ -7,8 +7,7 @@

namespace TestNs
{

public partial class TestVM
public partial class TestVM
{

/// <inheritdoc cref="_test4"/>
Expand All @@ -20,6 +19,7 @@ public int Test4
set
{
this.RaiseAndSetIfChanged(ref _test4, value);
this.RaisePropertyChanged(nameof(OtherNotifyProperty));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

namespace TestNs
{

public partial class TestVM
public partial class TestVM
{

/// <inheritdoc cref="_test3"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

namespace TestNs
{

public partial class TestVM
public partial class TestVM
{

/// <inheritdoc cref="_test1"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

namespace TestNs
{

public partial class TestVM
public partial class TestVM
{

/// <inheritdoc cref="this.value"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

namespace TestNs
{

public partial class TestVM
public partial class TestVM
{

/// <inheritdoc cref="_test2"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

namespace TestNs
{

public partial class TestVM
public partial class TestVM
{

/// <inheritdoc cref="_name"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

namespace TestNs1
{

public partial class TestVM
public partial class TestVM
{

/// <inheritdoc cref="_name"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

namespace TestNs2
{

public partial class TestVM
public partial class TestVM
{

/// <inheritdoc cref="_name"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

namespace TestNs
{

public partial class TestVM
public partial class TestVM
{

/// <inheritdoc cref="_mustBeSet"/>
Expand All @@ -25,4 +24,4 @@ public string MustBeSet
}
}
#nullable restore
#pragma warning restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ namespace TestNs1
{
public partial class TestViewModel3
{

public partial class TestInnerClass1
{

Expand Down Expand Up @@ -41,4 +40,4 @@ public int TestInner11

}
#nullable restore
#pragma warning restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ public partial class TestViewModel3
{
public partial class TestInnerClass2
{

public partial class TestInnerClass3
{

Expand Down Expand Up @@ -44,4 +43,4 @@ public int TestInner33

}
#nullable restore
#pragma warning restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ namespace TestNs1
{
public partial class TestViewModel3
{

public partial class TestInnerClass2
{

Expand Down Expand Up @@ -41,4 +40,4 @@ public int TestInner22

}
#nullable restore
#pragma warning restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

namespace TestNs1
{

public partial class TestViewModel3
public partial class TestViewModel3
{

/// <inheritdoc cref="_testVM3Property"/>
Expand Down Expand Up @@ -37,4 +36,4 @@ public float TestVM3Property2
}
}
#nullable restore
#pragma warning restore
#pragma warning restore
84 changes: 81 additions & 3 deletions src/ReactiveUI.SourceGenerator.Tests/TestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// The ReactiveUI and contributors licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

Expand All @@ -26,8 +27,8 @@ namespace ReactiveUI.SourceGenerator.Tests;
/// It provides utilities to initialize dependencies, run generators, and verify the output.
/// </summary>
/// <typeparam name="T">Type of Incremental Generator.</typeparam>
/// <seealso cref="System.IDisposable" />
public sealed class TestHelper<T> : IDisposable
/// <seealso cref="IDisposable" />
public sealed partial class TestHelper<T> : IDisposable
where T : IIncrementalGenerator, new()
{
/// <summary>
Expand Down Expand Up @@ -209,7 +210,7 @@ public SettingsTask RunGeneratorAndCheck(
if (rerunCompilation)
{
// Run the generator and capture diagnostics.
var rerunDriver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out var diagnostics);
var rerunDriver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var diagnostics);

// If any warnings or errors are found, log them to the test output before throwing an exception.
var offendingDiagnostics = diagnostics
Expand All @@ -226,12 +227,89 @@ public SettingsTask RunGeneratorAndCheck(
throw new InvalidOperationException("Compilation failed due to the above diagnostics.");
}

// Validate generated code contains expected features
ValidateGeneratedCode(code, rerunDriver);

return VerifyGenerator(rerunDriver);
}

// If rerun is not needed, simply run the generator.
return VerifyGenerator(driver.RunGenerators(compilation));
}

[GeneratedRegex(@"\[Reactive\((?:.*?nameof\((\w+)\))+", RegexOptions.Singleline)]
private static partial Regex ReactiveRegex();

[GeneratedRegex(@"nameof\((\w+)\)")]
private static partial Regex NameOfRegex();

/// <summary>
/// Validates that generated code contains expected features based on the source code attributes.
/// </summary>
/// <param name="sourceCode">The original source code.</param>
/// <param name="driver">The generator driver with generated output.</param>
private static void ValidateGeneratedCode(string sourceCode, GeneratorDriver driver)
{
var runResult = driver.GetRunResult();
var generatedTrees = runResult.Results.SelectMany(r => r.GeneratedSources).ToList();
var allGeneratedCode = string.Join("\n", generatedTrees.Select(t => t.SourceText.ToString()));

// Check for AlsoNotify feature in Reactive attributes
// Pattern matches: [Reactive(nameof(PropertyName))] or [Reactive(nameof(Prop1), nameof(Prop2))]
var alsoNotifyPattern = ReactiveRegex();
var nameofPattern = NameOfRegex();
var matches = alsoNotifyPattern.Matches(sourceCode);

TestContext.Out.WriteLine("=== VALIDATION DEBUG ===");
TestContext.Out.WriteLine("Found {0} Reactive attributes with nameof", matches.Count);

if (matches.Count > 0)
{
foreach (Match match in matches)
{
TestContext.Out.WriteLine("Checking attribute: {0}", match.Value);

// Extract all nameof() references within this attribute
var nameofMatches = nameofPattern.Matches(match.Value);
TestContext.Out.WriteLine("Found {0} nameof references in this attribute", nameofMatches.Count);

foreach (Match nameofMatch in nameofMatches)
{
var propertyToNotify = nameofMatch.Groups[1].Value;
TestContext.Out.WriteLine("Checking for notification of property: {0}", propertyToNotify);

// Verify that the generated code contains calls to raise property changed for the additional property
// Check for various forms of property change notification
var hasNotification =
allGeneratedCode.Contains($"this.RaisePropertyChanged(nameof({propertyToNotify}))") ||
allGeneratedCode.Contains($"this.RaisePropertyChanged(\"{propertyToNotify}\")") ||
allGeneratedCode.Contains($"RaisePropertyChanged(nameof({propertyToNotify}))") ||
allGeneratedCode.Contains($"RaisePropertyChanged(\"{propertyToNotify}\")");

TestContext.Out.WriteLine("Has notification: {0}", hasNotification);

if (!hasNotification)
{
var errorMessage = $"Generated code does not include AlsoNotify for property '{propertyToNotify}'. " +
$"Expected to find property change notification for '{propertyToNotify}' in the generated code.\n" +
$"Source attribute: {match.Value}";

TestContext.Out.WriteLine("=== VALIDATION FAILURE ===");
TestContext.Out.WriteLine(errorMessage);
TestContext.Out.WriteLine("=== SOURCE CODE SNIPPET ===");
TestContext.Out.WriteLine(match.Value);
TestContext.Out.WriteLine("=== GENERATED CODE ===");
TestContext.Out.WriteLine(allGeneratedCode);
TestContext.Out.WriteLine("=== END ===");

throw new InvalidOperationException(errorMessage);
}
}
}
}

TestContext.Out.WriteLine("=== END VALIDATION DEBUG ===");
}

private SettingsTask VerifyGenerator(GeneratorDriver driver) => Verify(driver).UseDirectory(VerifiedFilePath()).ScrubLinesContaining("[global::System.CodeDom.Compiler.GeneratedCode(\"");
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
}

var source = GenerateSource(grouping.Key.TargetName, grouping.Key.TargetNamespace, grouping.Key.TargetVisibility, grouping.Key.TargetType, grouping.FirstOrDefault());
context.AddSource(grouping.Key.FileHintName + ".IViewFor.g.cs", source);

// Only add source if it's not empty (i.e., a supported UI framework base type was detected)
if (!string.IsNullOrWhiteSpace(source))
{
context.AddSource(grouping.Key.FileHintName + ".IViewFor.g.cs", source);
}
}
});
}
Expand Down
Loading
Loading