Skip to content

Commit b53afa4

Browse files
committed
Switch to incremental source generator API
1 parent 49511f4 commit b53afa4

18 files changed

+1354
-243
lines changed

package-versions.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project>
22
<PropertyGroup>
33
<!-- Published dependencies (only update on major version change) -->
4-
<CodeAnalysisFrozenVersion>4.1.0</CodeAnalysisFrozenVersion>
4+
<CodeAnalysisFrozenVersion>4.13.0</CodeAnalysisFrozenVersion>
55
<DemystifierFrozenVersion>0.4.1</DemystifierFrozenVersion>
66
<HumanizerFrozenVersion>2.14.1</HumanizerFrozenVersion>
77
<NewtonsoftJsonFrozenVersion>13.0.4</NewtonsoftJsonFrozenVersion>
@@ -24,6 +24,7 @@
2424
<MiniValidationVersion>0.9.*</MiniValidationVersion>
2525
<NSwagApiClientVersion>14.6.*</NSwagApiClientVersion>
2626
<NewtonsoftJsonVersion>13.0.*</NewtonsoftJsonVersion>
27+
<PolyfillVersion>8.8.*</PolyfillVersion>
2728
<ReadableExpressionsVersion>4.1.*</ReadableExpressionsVersion>
2829
<ScalarAspNetCoreVersion>2.9.*</ScalarAspNetCoreVersion>
2930
<SwashbuckleVersion>9.*-*</SwashbuckleVersion>

src/JsonApiDotNetCore.SourceGenerators/ControllerSourceGenerator.cs

Lines changed: 225 additions & 87 deletions
Large diffs are not rendered by default.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using Microsoft.CodeAnalysis;
2+
3+
namespace JsonApiDotNetCore.SourceGenerators;
4+
5+
/// <summary>
6+
/// Basic outcome from the code analysis.
7+
/// </summary>
8+
internal readonly record struct CoreControllerInfo(
9+
TypeInfo ResourceType, TypeInfo IdType, string ControllerNamespace, JsonApiEndpointsCopy Endpoints, bool WriteNullableEnable)
10+
{
11+
// Using readonly fields, so they can be passed by reference (using 'in' modifier, to avoid making copies) during code generation.
12+
public readonly TypeInfo ResourceType = ResourceType;
13+
public readonly TypeInfo IdType = IdType;
14+
public readonly string ControllerNamespace = ControllerNamespace;
15+
public readonly JsonApiEndpointsCopy Endpoints = Endpoints;
16+
public readonly bool WriteNullableEnable = WriteNullableEnable;
17+
18+
public static CoreControllerInfo? TryCreate(INamedTypeSymbol resourceTypeSymbol, ITypeSymbol idTypeSymbol, JsonApiEndpointsCopy endpoints,
19+
string controllerNamespace)
20+
{
21+
TypeInfo? resourceTypeInfo = TypeInfo.CreateFromQualified(resourceTypeSymbol);
22+
TypeInfo? idTypeInfo = TypeInfo.TryCreateFromQualifiedOrPossiblyNullableKeyword(idTypeSymbol);
23+
24+
if (idTypeInfo == null)
25+
{
26+
return null;
27+
}
28+
29+
bool writeNullableEnable = idTypeSymbol is { IsReferenceType: true, NullableAnnotation: NullableAnnotation.Annotated };
30+
31+
return new CoreControllerInfo(resourceTypeInfo.Value, idTypeInfo.Value, controllerNamespace, endpoints, writeNullableEnable);
32+
}
33+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
namespace JsonApiDotNetCore.SourceGenerators;
2+
3+
/// <summary>
4+
/// Supplemental information that is derived from the core analysis, which is expensive to produce.
5+
/// </summary>
6+
internal readonly record struct FullControllerInfo(
7+
CoreControllerInfo CoreController, TypeInfo ControllerType, TypeInfo LoggerFactoryInterface, string HintFileName)
8+
{
9+
// Using readonly fields, so they can be passed by reference (using 'in' modifier, to avoid making copies) during code generation.
10+
public readonly CoreControllerInfo CoreController = CoreController;
11+
public readonly TypeInfo ControllerType = ControllerType;
12+
public readonly TypeInfo LoggerFactoryInterface = LoggerFactoryInterface;
13+
public readonly string HintFileName = HintFileName;
14+
15+
public static FullControllerInfo Create(CoreControllerInfo coreController, string controllerTypeName)
16+
{
17+
var controllerTypeInfo = new TypeInfo(coreController.ControllerNamespace, controllerTypeName);
18+
var loggerFactoryTypeInfo = new TypeInfo("Microsoft.Extensions.Logging", "ILoggerFactory");
19+
20+
return new FullControllerInfo(coreController, controllerTypeInfo, loggerFactoryTypeInfo, controllerTypeName);
21+
}
22+
23+
public FullControllerInfo WithHintFileName(string hintFileName)
24+
{
25+
// ReSharper disable once UseWithExpressionToCopyRecord
26+
// Justification: Workaround for bug at https://youtrack.jetbrains.com/issue/RSRP-502017/Invalid-suggestion-to-use-with-expression.
27+
return new FullControllerInfo(CoreController, ControllerType, LoggerFactoryInterface, hintFileName);
28+
}
29+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System.Collections.Immutable;
2+
3+
namespace JsonApiDotNetCore.SourceGenerators;
4+
5+
// This type was copied from Roslyn. The implementation looks odd, but is likely a performance tradeoff.
6+
// Beware that the consuming code doesn't adhere to the typical pattern where a dictionary is built once, then queried many times.
7+
8+
internal sealed class ImmutableDictionaryEqualityComparer<TKey, TValue> : IEqualityComparer<ImmutableDictionary<TKey, TValue>?>
9+
where TKey : notnull
10+
{
11+
public static readonly ImmutableDictionaryEqualityComparer<TKey, TValue> Instance = new();
12+
13+
public bool Equals(ImmutableDictionary<TKey, TValue>? x, ImmutableDictionary<TKey, TValue>? y)
14+
{
15+
if (ReferenceEquals(x, y))
16+
{
17+
return true;
18+
}
19+
20+
if (x is null || y is null)
21+
{
22+
return false;
23+
}
24+
25+
if (!Equals(x.KeyComparer, y.KeyComparer) || !Equals(x.ValueComparer, y.ValueComparer))
26+
{
27+
return false;
28+
}
29+
30+
foreach ((TKey key, TValue value) in x)
31+
{
32+
if (!y.TryGetValue(key, out TValue? other) || !x.ValueComparer.Equals(value, other))
33+
{
34+
return false;
35+
}
36+
}
37+
38+
return true;
39+
}
40+
41+
public int GetHashCode(ImmutableDictionary<TKey, TValue>? obj)
42+
{
43+
return obj?.Count ?? 0;
44+
}
45+
}

src/JsonApiDotNetCore.SourceGenerators/JsonApiDotNetCore.SourceGenerators.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
<IsPackable>true</IsPackable>
55
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
66
<IncludeBuildOutput>false</IncludeBuildOutput>
7-
<NoWarn>$(NoWarn);NU5128</NoWarn>
7+
<NoWarn>$(NoWarn);NU5128;RS2008</NoWarn>
88
<IsRoslynComponent>true</IsRoslynComponent>
9+
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
910
</PropertyGroup>
1011

1112
<Import Project="..\..\package-versions.props" />
@@ -47,5 +48,6 @@
4748
<ItemGroup>
4849
<PackageReference Include="Humanizer.Core" Version="$(HumanizerFrozenVersion)" PrivateAssets="all" GeneratePathProperty="true" />
4950
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="$(CodeAnalysisFrozenVersion)" PrivateAssets="all" />
51+
<PackageReference Include="Polyfill" Version="$(PolyfillVersion)" PrivateAssets="all" />
5052
</ItemGroup>
5153
</Project>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.Text;
3+
4+
namespace JsonApiDotNetCore.SourceGenerators;
5+
6+
internal readonly record struct LocationInfo(string FilePath, TextSpan TextSpan, LinePositionSpan LineSpan)
7+
{
8+
public static LocationInfo? CreateFrom(SyntaxNode node)
9+
{
10+
return CreateFrom(node.GetLocation());
11+
}
12+
13+
private static LocationInfo? CreateFrom(Location location)
14+
{
15+
if (location.SourceTree is null)
16+
{
17+
return null;
18+
}
19+
20+
return new LocationInfo(location.SourceTree.FilePath, location.SourceSpan, location.GetLineSpan().Span);
21+
}
22+
23+
public Location ToLocation()
24+
{
25+
return Location.Create(FilePath, TextSpan, LineSpan);
26+
}
27+
28+
public override string ToString()
29+
{
30+
return ToLocation().ToString();
31+
}
32+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
namespace JsonApiDotNetCore.SourceGenerators;
2+
3+
internal readonly record struct MissingInterfaceDiagnostic(string ResourceTypeName, LocationInfo? Location);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace JsonApiDotNetCore.SourceGenerators;
2+
3+
internal readonly record struct SemanticResult(CoreControllerInfo? CoreController, MissingInterfaceDiagnostic? Diagnostic)
4+
{
5+
public override string ToString()
6+
{
7+
if (Diagnostic != null)
8+
{
9+
return "Diagnostic";
10+
}
11+
12+
if (CoreController != null)
13+
{
14+
return "Controller";
15+
}
16+
17+
return string.Empty;
18+
}
19+
}

0 commit comments

Comments
 (0)