Skip to content

Commit

Permalink
Hook up basic SG and weaver
Browse files Browse the repository at this point in the history
  • Loading branch information
nirinchev committed Jul 10, 2024
1 parent 7b5d834 commit a240bec
Show file tree
Hide file tree
Showing 18 changed files with 1,913 additions and 1,205 deletions.
118 changes: 118 additions & 0 deletions Realm/Realm.SourceGenerator/ClassBuilders/ClassCodeBuilderBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2024 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License")
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;

namespace Realms.SourceGenerator;

internal abstract class ClassCodeBuilderBase
{
private readonly string[] _defaultNamespaces =
{
"MongoDB.Bson.Serialization",
"System",
"System.Collections.Generic",
"System.Linq",
"System.Runtime.CompilerServices",
"System.Runtime.Serialization",
"System.Xml.Serialization",
"System.Reflection",
"System.ComponentModel",
"Realms",
"Realms.Weaving",
"Realms.Schema",
};

protected readonly ClassInfo _classInfo;
protected readonly Lazy<string> _ignoreFieldAttribute;

protected string _baseInterface => $"I{_classInfo.ObjectType}";

Check warning on line 47 in Realm/Realm.SourceGenerator/ClassBuilders/ClassCodeBuilderBase.cs

View workflow job for this annotation

GitHub Actions / Test Weaver (ubuntu-latest, linux-x64)

Element '_baseInterface' should begin with an uppercase letter (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md)

Check warning on line 47 in Realm/Realm.SourceGenerator/ClassBuilders/ClassCodeBuilderBase.cs

View workflow job for this annotation

GitHub Actions / Test Weaver (macos-14, osx-arm64)

Element '_baseInterface' should begin with an uppercase letter (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md)

Check warning on line 47 in Realm/Realm.SourceGenerator/ClassBuilders/ClassCodeBuilderBase.cs

View workflow job for this annotation

GitHub Actions / Test Source Generation

Element '_baseInterface' should begin with an uppercase letter (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md)

Check warning on line 47 in Realm/Realm.SourceGenerator/ClassBuilders/ClassCodeBuilderBase.cs

View workflow job for this annotation

GitHub Actions / Test Weaver (windows-latest, win-x64)

Element '_baseInterface' should begin with an uppercase letter (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md)

Check warning on line 47 in Realm/Realm.SourceGenerator/ClassBuilders/ClassCodeBuilderBase.cs

View workflow job for this annotation

GitHub Actions / Analyze C#

Element '_baseInterface' should begin with an uppercase letter (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md)

Check warning on line 47 in Realm/Realm.SourceGenerator/ClassBuilders/ClassCodeBuilderBase.cs

View workflow job for this annotation

GitHub Actions / Package NuGet

Element '_baseInterface' should begin with an uppercase letter (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md)

Check warning on line 47 in Realm/Realm.SourceGenerator/ClassBuilders/ClassCodeBuilderBase.cs

View workflow job for this annotation

GitHub Actions / Package Unity

Element '_baseInterface' should begin with an uppercase letter (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md) [D:\a\realm-dotnet\realm-dotnet\Realm\Realm.SourceGenerator\Realm.SourceGenerator.csproj]

Check warning on line 47 in Realm/Realm.SourceGenerator/ClassBuilders/ClassCodeBuilderBase.cs

View workflow job for this annotation

GitHub Actions / Test Code Coverage

Element '_baseInterface' should begin with an uppercase letter (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md) [/home/runner/work/realm-dotnet/realm-dotnet/Realm/Realm.SourceGenerator/Realm.SourceGenerator.csproj]

protected ClassCodeBuilderBase(ClassInfo classInfo, GeneratorConfig generatorConfig)
{
_classInfo = classInfo;

_ignoreFieldAttribute = new(() =>
{
var result = "[IgnoreDataMember, XmlIgnore]";
var customAttribute = generatorConfig.CustomIgnoreAttribute;
if (!string.IsNullOrEmpty(customAttribute))
{
result += customAttribute;
}

return result;
});
}

public static ClassCodeBuilderBase CreateBuilder(ClassInfo classInfo, GeneratorConfig generatorConfig) => classInfo.ObjectType switch
{
ObjectType.MappedObject => new MappedObjectCodeBuilder(classInfo, generatorConfig),
ObjectType.AsymmetricObject or ObjectType.EmbeddedObject or ObjectType.RealmObject => new RealmObjectCodeBuilder(classInfo, generatorConfig),
_ => throw new NotSupportedException($"Unexpected ObjectType: {classInfo.ObjectType}")
};

public string GenerateSource()
{
var usings = GetUsings();

var classString = GeneratePartialClass();

foreach (var enclosingClass in _classInfo.EnclosingClasses)
{
classString = $@"{SyntaxFacts.GetText(enclosingClass.Accessibility)} partial class {enclosingClass.Name}
{{
{classString.Indent()}
}}";
}

if (!_classInfo.NamespaceInfo.IsGlobal)
{
classString = $@"namespace {_classInfo.NamespaceInfo.OriginalName}
{{
{classString.Indent()}
}}";
}

return $@"// <auto-generated />
#nullable enable
{usings}
{classString}
";
}

private string GetUsings()
{
var namespaces = new HashSet<string>(_defaultNamespaces);
namespaces.UnionWith(_classInfo.Usings);

if (!_classInfo.NamespaceInfo.IsGlobal)
{
namespaces.Add(_classInfo.NamespaceInfo.OriginalName);
}

return string.Join(Environment.NewLine, namespaces.Where(n => !string.IsNullOrWhiteSpace(n)).OrderBy(s => s).Select(s => $"using {s};"));
}

protected abstract string GeneratePartialClass();
}
114 changes: 114 additions & 0 deletions Realm/Realm.SourceGenerator/ClassBuilders/MappedObjectCodeBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2024 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License")
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

using Microsoft.CodeAnalysis.CSharp;

namespace Realms.SourceGenerator;

internal class MappedObjectCodeBuilder : ClassCodeBuilderBase
{
public MappedObjectCodeBuilder(ClassInfo classInfo, GeneratorConfig generatorConfig) : base(classInfo, generatorConfig)
{
}

protected override string GeneratePartialClass()
{
var contents = @"private IDictionary<string, RealmValue> _backingStorage = null!;
public void SetBackingStorage(IDictionary<string, RealmValue> dictionary)
{
_backingStorage = dictionary;
}";

return $@"[Generated]
[Realms.Preserve(AllMembers = true)]
{SyntaxFacts.GetText(_classInfo.Accessibility)} partial class {_classInfo.Name} : {_baseInterface}, INotifyPropertyChanged
{{
{contents.Indent()}
{GetPropertyChanged().Indent()}
}}";
}

private string GetPropertyChanged()
{
return _classInfo.HasPropertyChangedEvent ? string.Empty :
@"#region INotifyPropertyChanged
private IDisposable? _notificationToken;
private event PropertyChangedEventHandler? _propertyChanged;
/// <inheritdoc />
public event PropertyChangedEventHandler? PropertyChanged
{
add
{
if (_propertyChanged == null)
{
SubscribeForNotifications();
}
_propertyChanged += value;
}
remove
{
_propertyChanged -= value;
if (_propertyChanged == null)
{
UnsubscribeFromNotifications();
}
}
}
partial void OnPropertyChanged(string? propertyName);
private void RaisePropertyChanged([CallerMemberName] string propertyName = """")
{
_propertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
OnPropertyChanged(propertyName);
}
private void SubscribeForNotifications()
{
_notificationToken = _backingStorage.SubscribeForKeyNotifications((sender, changes) =>
{
if (changes == null)
{
return;
}
foreach (var key in changes.ModifiedKeys)
{
RaisePropertyChanged(key);
}
// TODO: what do we do with deleted/inserted keys

Check warning on line 103 in Realm/Realm.SourceGenerator/ClassBuilders/MappedObjectCodeBuilder.cs

View workflow job for this annotation

GitHub Actions / Verify TODOs

Realm/Realm.SourceGenerator/ClassBuilders/MappedObjectCodeBuilder.cs#L103

TODO entry doesn't have a link to Github issue or Jira ticket what do we do with deleted/inserted keys
});
}
private void UnsubscribeFromNotifications()
{
_notificationToken?.Dispose();
}
#endregion";
}
}
Loading

0 comments on commit a240bec

Please sign in to comment.