-
-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6de57e8
commit 471581c
Showing
8 changed files
with
268 additions
and
37 deletions.
There are no files selected for viewing
20 changes: 20 additions & 0 deletions
20
src/Blazor.SourceGenerators/Extensions/KeyValuePairExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// Copyright (c) David Pine. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
namespace Blazor.SourceGenerators.Extensions; | ||
|
||
internal static class KeyValuePairExtensions | ||
{ | ||
/// <summary> | ||
/// Extends the <see cref="KeyValuePair{TKey, TValue}"/> type, | ||
/// by exposing the <c>Deconstruct</c> functionality. Meaning that | ||
/// we can now do the following: | ||
/// <c>foreach (var (key, value) in Dictionary) { ... }</c> | ||
/// </summary> | ||
/// <typeparam name="TKey">The key <c>TKey</c> type.</typeparam> | ||
/// <typeparam name="TValue">The value <c>TValue</c> type.</typeparam> | ||
internal static void Deconstruct<TKey, TValue>( | ||
this KeyValuePair<TKey, TValue> kvp, | ||
out TKey key, | ||
out TValue value) => (key, value) = (kvp.Key, kvp.Value); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
src/Blazor.SourceGenerators/Types/DependentTypeMapper.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// Copyright (c) David Pine. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.Reflection.Metadata; | ||
using Blazor.SourceGenerators.TypeScript; | ||
using Blazor.SourceGenerators.TypeScript.Types; | ||
|
||
namespace Blazor.SourceGenerators.Types; | ||
|
||
internal static class DependentTypeMapper | ||
{ | ||
private static readonly Lazy<ITypeScriptAbstractSyntaxTree> s_lazyDefaultAst = new(() => | ||
{ | ||
var reader = TypeDeclarationReader.Default; | ||
return TypeScriptAbstractSyntaxTree.FromSourceText(reader.RawSourceText); | ||
}); | ||
|
||
/// <summary> | ||
/// Gets a <see cref="Dictionary{TKey, TValue}"/> where the <c>TKey</c> is a | ||
/// <see cref="string"/> and the <c>TValue</c> is a <see cref="Node"/>. | ||
/// </summary> | ||
/// <param name="interfaceName">The name of the <c>Name</c> in the TypeScript | ||
/// _.d.ts_ file's <c>interface name { ... }</c> definition.</param> | ||
/// <returns>A representation of all the dependent types as a | ||
/// <see cref="Dictionary{TKey, TValue}"/> where the <c>TKey</c> is a | ||
/// <see cref="string"/> and the <c>TValue</c> is a <see cref="Node"/>. | ||
/// Returns empty when the underlying AST parser is null or has no | ||
/// root node, or if there isn't an <c>interface</c> found. | ||
/// </returns> | ||
public static Dictionary<string, Node> GetDependentTypeMap(string interfaceName) | ||
{ | ||
var ast = s_lazyDefaultAst.Value; | ||
if (ast is null or { RootNode: null }) | ||
{ | ||
return new(); | ||
} | ||
|
||
var rootNode = ast.RootNode; | ||
|
||
var interfaceNode = rootNode.Children | ||
.OfKind(TypeScriptSyntaxKind.InterfaceDeclaration) | ||
.FirstOrDefault(type => type.Identifier == interfaceName); | ||
|
||
if (interfaceNode is null) | ||
{ | ||
return new Dictionary<string, Node>(); | ||
} | ||
|
||
var dependentTypes = new Dictionary<string, Node> | ||
{ | ||
[interfaceNode.Identifier] = interfaceNode | ||
}; | ||
|
||
var queue = new Queue<Node>(); | ||
queue.Enqueue(interfaceNode); | ||
|
||
while (queue.Count > 0) | ||
{ | ||
var node = queue.Dequeue(); | ||
|
||
// Get methods | ||
var methods = | ||
node.OfKind(TypeScriptSyntaxKind.MethodSignature).Cast<MethodSignature>(); | ||
|
||
foreach (var method in methods) | ||
{ | ||
// Get method parameters | ||
var parameters = | ||
method.OfKind(TypeScriptSyntaxKind.Parameter).Cast<ParameterDeclaration>(); | ||
|
||
foreach (var parameter in parameters) | ||
{ | ||
// Get parameter type | ||
var typeReferences = | ||
parameter.OfKind(TypeScriptSyntaxKind.TypeReference) | ||
.Cast<TypeReferenceNode>(); | ||
|
||
foreach (var typeReference in typeReferences) | ||
{ | ||
dependentTypes[typeReference.Identifier] = typeReference; | ||
queue.Enqueue(typeReference); | ||
|
||
foreach ((var key, var @interface) in | ||
GetDependentTypeMap(interfaceName: typeReference.Identifier)) | ||
{ | ||
dependentTypes[@interface.Identifier] = @interface; | ||
queue.Enqueue(@interface); | ||
} | ||
} | ||
} | ||
|
||
// Get method return type | ||
var returnTypeReferences = | ||
method.OfKind(TypeScriptSyntaxKind.TypeReference) | ||
.Cast<TypeReferenceNode>(); | ||
|
||
foreach (var typeReference in returnTypeReferences) | ||
{ | ||
dependentTypes[typeReference.Identifier] = typeReference; | ||
queue.Enqueue(typeReference); | ||
} | ||
} | ||
|
||
// Get properties | ||
var properties = | ||
node.OfKind(TypeScriptSyntaxKind.PropertySignature) | ||
.Cast<PropertySignature>(); | ||
|
||
foreach (var property in properties) | ||
{ | ||
dependentTypes[property.Identifier] = property; | ||
queue.Enqueue(property); | ||
} | ||
|
||
foreach (var childNode in node.Children) | ||
{ | ||
queue.Enqueue(childNode); | ||
} | ||
} | ||
|
||
return dependentTypes; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// Copyright (c) David Pine. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System.Diagnostics; | ||
using Blazor.SourceGenerators.Types; | ||
using Blazor.SourceGenerators.TypeScript.Types; | ||
using Xunit; | ||
|
||
namespace Blazor.SourceGenerators.Tests; | ||
|
||
public class TypeMapperTests | ||
{ | ||
[Fact] | ||
public void TypeMapperCorrectlyMapsKnownTypeMap() | ||
{ | ||
static Dictionary<string, Node> GetTypeMapWithPotential(bool timePenalty) | ||
{ | ||
var startingTimestamp = Stopwatch.GetTimestamp(); | ||
var sut = DependentTypeMapper.GetDependentTypeMap; | ||
|
||
// The implementation: window.navigator.geolocation | ||
var typeMap = sut("Geolocation"); | ||
|
||
Assert.NotEmpty(typeMap); | ||
var elapsedTimestamp = Stopwatch.GetElapsedTime(startingTimestamp); | ||
|
||
// This needs to take less than a second. | ||
// But only fail the test if there is a time penalty. | ||
Assert.True( | ||
condition: timePenalty is false || | ||
elapsedTimestamp.TotalMilliseconds < 1_000, $""" | ||
condition: timePenalty is {timePenalty is false} | ||
or took longer than 1,000ms {elapsedTimestamp.TotalMilliseconds < 1_000}. | ||
"""); | ||
|
||
return typeMap; | ||
} | ||
|
||
var typeMap = GetTypeMapWithPotential(timePenalty: false); | ||
|
||
Assert.NotNull(typeMap["Geolocation"]); | ||
Assert.NotNull(typeMap["PositionCallback"]); | ||
Assert.NotNull(typeMap["PositionErrorCallback"]); | ||
Assert.NotNull(typeMap["PositionOptions"]); | ||
|
||
// TODO: these types should be present, but they're not... | ||
// Assert.NotNull(typeMap["GeolocationPosition"]); | ||
// Assert.NotNull(typeMap["GeolocationPositionError"]); | ||
// Assert.NotNull(typeMap["GeolocationCoordinates"]); | ||
} | ||
} |