Skip to content

Commit

Permalink
Merge branch 'main' into ni/evg-tests
Browse files Browse the repository at this point in the history
* main:
  Small fixes to source generator (#3466)
  Make websocket error logging exceptionally verbose (#3459)
  Emit errors for collection assignments (#3456)
  Fix docfx source files (#3453)
  Fix typo (#3454)
  Add User.Changed event (#3433)
  Allow customizing json ignore attribute for serialized classes (#3451)
  Update README.md (#3450)
  Fix changelog
  Prepare for vNext (#3444)
  Prepare for 11.5.0 (#3442)
  • Loading branch information
nirinchev committed Nov 1, 2023
2 parents b8e673c + 10aaf6a commit a34e397
Show file tree
Hide file tree
Showing 168 changed files with 842 additions and 289 deletions.
24 changes: 22 additions & 2 deletions .github/templates/build-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,40 @@
uses: #@ actionCache
with:
path: 'C:\docfx'
key: docfx-2.62.1
key: docfx-2.70.4
- name: Download docfx
if: inputs.build-docs && steps.check-docfx-cache.outputs.cache-hit != 'true'
run: |
Invoke-WebRequest -Uri https://github.com/dotnet/docfx/releases/download/v2.62.1/docfx-win-x64-v2.62.1.zip -OutFile C:\docfx.zip
Invoke-WebRequest -Uri https://github.com/dotnet/docfx/releases/download/v2.70.4/docfx-win-x64-v2.70.4.zip -OutFile C:\docfx.zip
Expand-Archive -Path C:\docfx.zip -DestinationPath C:\docfx
shell: powershell
- _: #@ template.replace(setupDotnet("6.0.402", ifCondition = "inputs.build-docs"))
- name: Build docs
if: inputs.build-docs
env:
DOCFX_SOURCE_BRANCH_NAME: ${{ github.head_ref }}
run: |
New-Item global.json
Set-Content global.json '{ "sdk": { "version": "6.0.402" } }'
C:\docfx\docfx Docs/docfx.json
#! the link generated by docfx is incorrect - it points to
#! https://github.com/realm/realm-dotnet/new/main/Docs/apispec/new?filename=...
#! instead of
#! https://github.com/realm/realm-dotnet/new/main/Docs/apispec?filename=...
- name: Update Improve this doc links
run: |
Get-ChildItem Docs/_site -Filter *.html -Recurse -File |
ForEach-Object {
$content = ($_ | Get-Content -Raw)
$content = $content -replace "/Docs/apispec/new\?filename", "/Docs/apispec?filename"
Set-Content $_.FullName $content
}
shell: pwsh

- name: Archive docs
if: inputs.build-docs
run: |
Compress-Archive -Path Docs/_site -DestinationPath "Realm/packages/Docs.zip"
- _: #@ template.replace(uploadArtifacts("Docs.zip", "Realm/packages/Docs.zip"))
if: inputs.build-docs
Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/build-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,24 @@ jobs:
dotnet-version: 6.0.402
- name: Build docs
if: inputs.build-docs
env:
DOCFX_SOURCE_BRANCH_NAME: ${{ github.head_ref }}
run: |
New-Item global.json
Set-Content global.json '{ "sdk": { "version": "6.0.402" } }'
C:\docfx\docfx Docs/docfx.json
- name: Update Improve this doc links
run: |
Get-ChildItem Docs/_site -Filter *.html -Recurse -File |
ForEach-Object {
$content = ($_ | Get-Content -Raw)
$content = $content -replace "/Docs/apispec/new\?filename", "/Docs/apispec?filename"
Set-Content $_.FullName $content
}
shell: pwsh
- name: Archive docs
if: inputs.build-docs
run: |
Compress-Archive -Path Docs/_site -DestinationPath "Realm/packages/Docs.zip"
- name: Store artifacts for Docs.zip
uses: actions/upload-artifact@v3
Expand Down
31 changes: 30 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,46 @@
## 11.5.0 (2023-09-15)

### Enhancements
* Added `User.Changed` event that can be used to notify subscribers that something about the user changed - typically this would be the user state or the access token. (Issue [#3429](https://github.com/realm/realm-dotnet/issues/3429))
* Added support for customizing the ignore attribute applied on certain generated properties of Realm models. The configuration option is called `realm.custom_ignore_attribute` and can be set in a global configuration file (more information about global configuration files can be found in the [.NET documentation](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-files)). The Realm generator will treat this as an opaque string, that will be appended to the `IgnoreDataMember` and `XmlIgnore` attributes already applied on these members. The attributes must be fully qualified unless the namespace they reside in is added to a global usings file. For example, this is how you would add `JsonIgnore` from `System.Text.Json`:

```
realm.custom_ignore_attribute = [System.Text.Json.Serialization.JsonIgnore]
```
(Issue [#2579](https://github.com/realm/realm-dotnet/issues/2579))
* The Realm source generator will now error out in case a collection in the model classes is assigned to a non-null value either in a property initializer or in a constructor. Realm collections are initialized internally and assigning non-null values to the property is not supported, where the `null!` assignment is only useful to silence nullable reference type warnings, in reality the collection will never be null. (Issue [#3455](https://github.com/realm/realm-dotnet/issues/3455))
* Made WebSocket error logging more verbose when using `AppConfiguration.UseManagedWebSockets = true`. [#3459](https://github.com/realm/realm-dotnet/pull/3459)

### Fixed
* Added an error that is raised when interface based Realm classes are used with a language version lower than 8.0. At the same time, removed the use of `not` in the generated code, so that it's compatible with a minumum C# version of 8.0. (Issue [#3265](https://github.com/realm/realm-dotnet/issues/3265))

### Compatibility
* Realm Studio: 13.0.0 or later.

### Internal
* Using Core 13.20.1.

## 11.5.0 (2023-09-15)

### Enhancements
* Streamlined some of the error codes reported in `SessionException`. A few error codes have been combined and some have been deprecated since they are no longer reported by the server. (Issue [#3295](https://github.com/realm/realm-dotnet/issues/3295))
* Full text search supports searching for prefix only. Eg. "description TEXT 'alex*'". (Core 13.18.0)
* Unknown protocol errors received from Atlas Device Sync will no longer cause the application to crash if a valid error action is also received. Unknown error actions will be treated as an ApplicationBug error action and will cause sync to fail with an error via the sync error handler. (Core 13.18.0)
* Added support for server log messages that are enabled by sync protocol version 10. Appservices request id will be provided in a server log message in a future server release. (Core 13.19.0)

### Fixed
* Fixed the message of the `MissingMemberException` being thrown when attempting to access a non-existent property with the dynamic API. (PR [#3432](https://github.com/realm/realm-dotnet/pull/3432))
* Fixed a `Cannot marshal generic Windows Runtime types with a non Windows Runtime type as a generic type argument` build error when using .NET Native. (Issue [#3434](https://github.com/realm/realm-dotnet/issues/3434), since 11.4.0)
* Fix failed assertion for unknown app server errors. (Core 13.17.2)
* Running a query on @keys in a Dictionary would throw an exception. (Core 13.17.2)
* Fixed crash in slab allocator (`Assertion failed: ref + size <= next->first`). (Core 13.20.1)
* Sending empty UPLOAD messages may lead to 'Bad server version' errors and client reset. (Core 13.20.1)

### Compatibility
* Realm Studio: 13.0.0 or later.

### Internal
* Using Core x.y.z.
* Using Core 13.20.1.

## 11.4.0 (2023-08-16)

Expand Down
5 changes: 3 additions & 2 deletions Docs/docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"filter": "filterConfig.yml",
"properties": {
"TargetFramework": "netstandard2.0"
}
},
"EnumSortOrder": "declaringOrder"
}
],
"build": {
Expand Down Expand Up @@ -82,7 +83,7 @@
"_gitContribute": {
"repo": "https://github.com/realm/realm-dotnet.git",
"branch": "main",
"path": "Docs/apispec"
"apiSpecFolder": "Docs/apispec"
}
},
"fileMetadataFiles": [],
Expand Down
2 changes: 2 additions & 0 deletions Realm/Realm.PlatformHelpers/Realm.PlatformHelpers.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
<LangVersion>9.0</LangVersion>
<nullable>enable</nullable>
<AndroidResgenNamespace>Realms.PlatformHelpers</AndroidResgenNamespace>
<!-- NETSDK1202 warns about deprecated workflows (i.e. net6.0-(mobile)) - we still target them to cover broader developer audience -->
<NoWarn>NETSDK1202</NoWarn>
</PropertyGroup>

<ItemGroup>
Expand Down
37 changes: 25 additions & 12 deletions Realm/Realm.SourceGenerator/ClassCodeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,29 @@ internal class ClassCodeBuilder
};

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

private readonly string _helperClassName;
private readonly string _accessorInterfaceName;
private readonly string _managedAccessorClassName;
private readonly string _unmanagedAccessorClassName;

public ClassCodeBuilder(ClassInfo classInfo)
public ClassCodeBuilder(ClassInfo classInfo, GeneratorConfig generatorConfig)
{
_classInfo = classInfo;

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

return result;
});

var className = _classInfo.Name;

_helperClassName = $"{className}ObjectHelper";
Expand Down Expand Up @@ -287,36 +300,36 @@ private string GeneratePartialClass(string interfaceString, string managedAccess
private {_accessorInterfaceName} Accessor => _accessor ??= new {_unmanagedAccessorClassName}(typeof({_classInfo.Name}));
/// <inheritdoc />
[IgnoreDataMember, XmlIgnore]
{_ignoreFieldAttribute.Value}
public bool IsManaged => Accessor.IsManaged;
/// <inheritdoc />
[IgnoreDataMember, XmlIgnore]
{_ignoreFieldAttribute.Value}
public bool IsValid => Accessor.IsValid;
/// <inheritdoc />
[IgnoreDataMember, XmlIgnore]
{_ignoreFieldAttribute.Value}
public bool IsFrozen => Accessor.IsFrozen;
/// <inheritdoc />
[IgnoreDataMember, XmlIgnore]
{_ignoreFieldAttribute.Value}
public Realms.Realm? Realm => Accessor.Realm;
/// <inheritdoc />
[IgnoreDataMember, XmlIgnore]
{_ignoreFieldAttribute.Value}
public Realms.Schema.ObjectSchema ObjectSchema => Accessor.ObjectSchema!;
/// <inheritdoc />
[IgnoreDataMember, XmlIgnore]
{_ignoreFieldAttribute.Value}
public Realms.DynamicObjectApi DynamicApi => Accessor.DynamicApi;
/// <inheritdoc />
[IgnoreDataMember, XmlIgnore]
{_ignoreFieldAttribute.Value}
public int BacklinksCount => Accessor.BacklinksCount;
{(_classInfo.ObjectType != ObjectType.EmbeddedObject ? string.Empty :
@"/// <inheritdoc />
[IgnoreDataMember, XmlIgnore]
$@"/// <inheritdoc />
{_ignoreFieldAttribute.Value}
public Realms.IRealmObjectBase? Parent => Accessor.GetParent();")}
void ISettableManagedAccessor.SetManagedAccessor(Realms.IRealmAccessor managedAccessor, Realms.Weaving.IRealmObjectHelper? helper, bool update, bool skipDefaults)
Expand Down Expand Up @@ -463,7 +476,7 @@ public override bool Equals(object? obj)
return !IsValid;
}
if (obj is not Realms.IRealmObjectBase iro)
if (!(obj is Realms.IRealmObjectBase iro))
{
return false;
}
Expand Down Expand Up @@ -834,7 +847,7 @@ private string GenerateManagedAccessor()
}
else
{
var forceNotNullable = type == "string" || type == "byte[]" ? "!" : string.Empty;
var forceNotNullable = type is "string" or "byte[]" ? "!" : string.Empty;

var getterString = $@"get => ({type})GetValue(""{stringName}""){forceNotNullable};";

Expand Down
11 changes: 5 additions & 6 deletions Realm/Realm.SourceGenerator/CodeEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ namespace Realms.SourceGenerator
internal class CodeEmitter
{
private readonly GeneratorExecutionContext _context;
private readonly GeneratorConfig _generatorConfig;

public CodeEmitter(GeneratorExecutionContext context)
public CodeEmitter(GeneratorExecutionContext context, GeneratorConfig generatorConfig)
{
_context = context;
_generatorConfig = generatorConfig;
}

public void Emit(ParsingResults parsingResults)
Expand All @@ -45,7 +47,7 @@ public void Emit(ParsingResults parsingResults)

try
{
var generatedSource = new ClassCodeBuilder(classInfo).GenerateSource();
var generatedSource = new ClassCodeBuilder(classInfo, _generatorConfig).GenerateSource();

// Replace all occurrences of at least 3 newlines with only 2
var formattedSource = Regex.Replace(generatedSource, @$"[{Environment.NewLine}]{{3,}}", $"{Environment.NewLine}{Environment.NewLine}");
Expand All @@ -65,9 +67,6 @@ public void Emit(ParsingResults parsingResults)
}
}

private static bool ShouldEmit(ClassInfo classInfo)
{
return !classInfo.Diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error);
}
private static bool ShouldEmit(ClassInfo classInfo) => classInfo.Diagnostics.All(d => d.Severity != DiagnosticSeverity.Error);
}
}
44 changes: 43 additions & 1 deletion Realm/Realm.SourceGenerator/Diagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,32 @@ private enum Id
RealmObjectWithoutAutomaticProperty = 25,
ParentOfNestedClassIsNotPartial = 27,
IndexedPrimaryKey = 28,
InvalidCollectionInitializer = 29,
InvalidCollectionInitializerInCtor = 30,
OldCSharpVersion = 100,
InvalidGeneratorConfiguration = 1000,
}

#region Errors

public static Diagnostic InvalidConfiguration(string field, string description)
{
return CreateDiagnosticError(
Id.InvalidGeneratorConfiguration,
"Invalid source generator configuration",
$"The generator configuration for {field} is invalid: {description}",
Location.None);
}

public static Diagnostic OldCSharpVersion()
{
return CreateDiagnosticError(
Id.OldCSharpVersion,
"Unsupported version of C#",
$"It is not possible to use the Realm source generator with C# versions older than 8.0.",
Location.None);
}

public static Diagnostic UnexpectedError(string className, string message, string stackTrace)
{
return CreateDiagnosticError(
Expand All @@ -70,7 +92,7 @@ public static Diagnostic ClassUnclearDefinition(string className, Location locat
return CreateDiagnosticError(
Id.ClassUnclearDefinition,
"Realm classes cannot implement multiple class interfaces",
$"Class {className} is declared as implementing multiple class interfaces.A class can implement only one interface between IRealmObject, IEmbeddedObject, IAsymmetricObject.",
$"Class {className} is declared as implementing multiple class interfaces. A class can implement only one interface between IRealmObject, IEmbeddedObject, IAsymmetricObject.",
location);
}

Expand Down Expand Up @@ -308,6 +330,26 @@ public static Diagnostic ParentOfNestedClassIsNotPartial(string className, strin
location);
}

public static Diagnostic InvalidCollectionInitializer(string className, string propertyName, Location location)
{
return CreateDiagnosticError(
Id.InvalidCollectionInitializer,
"Invalid collection initializer",
$"{className}.{propertyName} is a collection with an initializer that is not supported. Realm collections are always initialized internally and initializing them to a non-null value is not supported.",
location,
description: $"Either remove the initializer or replace it with '= null!' to silence the 'Non-nullable field '{propertyName}' is uninitialized' error.");
}

public static Diagnostic InvalidCollectionInitializerInCtor(string className, string propertyName, Location location)
{
return CreateDiagnosticError(
Id.InvalidCollectionInitializerInCtor,
"Invalid collection initializer in constructor",
$"{className}.{propertyName} is a collection that is initialized in a constructor. Realm collections are always initialized internally and initializing them to a non-null value is not supported.",
location,
description: $"Either remove the initializer or replace it with '= null!' to silence the 'Non-nullable field '{propertyName}' is uninitialized' error.");
}

#endregion

#region Warnings
Expand Down
26 changes: 18 additions & 8 deletions Realm/Realm.SourceGenerator/DiagnosticsEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,30 @@ namespace Realms.SourceGenerator
{
internal class DiagnosticsEmitter
{
private GeneratorExecutionContext _context;
private readonly GeneratorExecutionContext _context;

public DiagnosticsEmitter(GeneratorExecutionContext context)
public DiagnosticsEmitter(GeneratorExecutionContext context, GeneratorConfig generatorConfig)
{
_context = context;
}

public void Emit(ParsingResults parsingResults)
{
foreach (var classInfo in parsingResults.ClassInfo)
var customIgnoreAttribute = generatorConfig.CustomIgnoreAttribute;
if (!string.IsNullOrEmpty(customIgnoreAttribute))
{
if (!classInfo.Diagnostics.Any())
if (!customIgnoreAttribute!.StartsWith("[") || !customIgnoreAttribute.EndsWith("]"))
{
continue;
_context.ReportDiagnostic(Diagnostics.InvalidConfiguration(
field: "realm.custom_ignore_attribute",
description: $"The attribute(s) string should start with '[' and end with ']'. Actual value: {customIgnoreAttribute}."));

generatorConfig.CustomIgnoreAttribute = null;
}
}
}

public void Emit(ParsingResults parsingResults)
{
foreach (var classInfo in parsingResults.ClassInfo.Where(classInfo => classInfo.Diagnostics.Any()))
{
try
{
SerializeDiagnostics(_context, classInfo);
Expand All @@ -51,6 +59,8 @@ public void Emit(ParsingResults parsingResults)
throw;
}
}

parsingResults.GeneralDiagnostics.ForEach(_context.ReportDiagnostic);
}

private static void SerializeDiagnostics(GeneratorExecutionContext context, ClassInfo classInfo)
Expand Down
Loading

0 comments on commit a34e397

Please sign in to comment.