react-native-windows/vnext/Microsoft.ReactNative.Manag.../CodeGenerator.Module.cs

516 строки
20 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// cspell:ignore evnt
using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.ReactNative.Managed.CodeGen.Model;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Microsoft.ReactNative.Managed.CodeGen.SyntaxHelpers;
namespace Microsoft.ReactNative.Managed.CodeGen
{
public partial class CodeGenerator
{
internal MemberDeclarationSyntax CreateModules(IEnumerable<ReactModule> modules)
{
var addModuleStatements = new List<StatementSyntax>();
foreach (var module in modules)
{
var addMemberStatements = new List<StatementSyntax>();
// generates:
// MyModule module = new MyModule();
addMemberStatements.Add(
LocalDeclarationStatement(module.ModuleType, ReactNativeNames.Module, ObjectCreationExpression(module.ModuleType)));
if (module.Constants.Any())
{
addMemberStatements.Add(AddConstants(module.Constants));
}
foreach (var constantProvider in module.ConstantProviders)
{
addMemberStatements.Add(AddConstantProvider(constantProvider));
}
foreach (var getConstants in module.GetConstants)
{
addMemberStatements.Add(AddGetConstants(getConstants));
}
foreach (var method in module.Methods)
{
addMemberStatements.Add(AddMethod(method));
}
foreach (var evnt in module.Events)
{
addMemberStatements.Add(AddEvent(evnt));
}
foreach (var function in module.Functions)
{
addMemberStatements.Add(AddFunction(function));
}
foreach (var initializer in module.Initializers)
{
addMemberStatements.Add(AddInitializer(initializer));
}
// generates
// return module;
addMemberStatements.Add(
ReturnStatement(IdentifierName(ReactNativeNames.Module)));
// Generates:
// packageBuilder.AddModule("MyViewManager", () => new global::MyApp.ViewManager);
addModuleStatements.Add(
InvocationStatement(
MemberAccessExpression(ReactNativeNames.PackageBuilderId, ReactNativeNames.AddModule),
LiteralExpression(module.ModuleName),
ParenthesizedLambdaExpression(
parameterList: ParameterList(
Parameter(ReactNativeNames.ModuleBuilder)
.WithType(ReactTypes.IReactModuleBuilder.ToTypeSyntax())),
block: Block(
addMemberStatements),
expressionBody: null)
)
);
}
// Generates:
// internal void CreateModules(IPackageBuilder packageBuilder)
// {
// ... registrationCalls (ses above)
// }
return MethodDeclaration(
PredefinedType(Token(SyntaxKind.VoidKeyword)),
ReactNativeNames.CreateModules)
.AddModifiers(
Token(SyntaxKind.InternalKeyword))
.AddParameterListParameters(
GetPackageBuilderArgument())
.WithBody(
Block(
addModuleStatements));
}
internal StatementSyntax AddConstants(IEnumerable<ReactConstant> constants)
{
// place all constants in a single constant provider
var statements = new List<StatementSyntax>();
// constants
foreach (var constant in constants)
{
// generates:
// JSValueWriter.WriteObjectProperty(writer, "MyConst", module.MyConst);
statements.Add(
InvocationStatement(
MemberAccessExpression(ReactTypes.JSValueWriter, ReactNativeNames.WriteObjectPropertyMethodName),
IdentifierName(ReactNativeNames.WriterLocalName),
LiteralExpression(constant.Name),
MemberAccessExpression(ReactNativeNames.Module, Identifier(constant.Symbol.Name))));
}
// generates:
// moduleBuilder.AddConstantProvider( (IJSValueWriter writer) => { ... } );
return InvocationStatement(
MemberAccessExpression(ReactNativeNames.ModuleBuilder, ReactNativeNames.AddConstantProvider),
ParenthesizedLambdaExpression(
parameterList: ParameterList(
Parameter(ReactNativeNames.WriterLocalName)
.WithType(ReactTypes.IJSValueWriter.ToTypeSyntax())),
block: Block(
statements),
expressionBody: null
)
);
}
internal StatementSyntax AddConstantProvider(ReactConstantProvider constantProvider)
{
// generates:
// moduleBuilder.AddConstantProvider( (IJSValueWriter writer) => {
// var provider = new ReactConstantProvider(writer);
// module.ConstantProviderName(provider);
// });
return InvocationStatement(
MemberAccessExpression(ReactNativeNames.ModuleBuilder, ReactNativeNames.AddConstantProvider),
ParenthesizedLambdaExpression(
parameterList: ParameterList(
Parameter(ReactNativeNames.WriterLocalName)
.WithType(ReactTypes.IJSValueWriter.ToTypeSyntax())),
block: Block(
LocalDeclarationStatement(
ReactTypes.ReactConstantProvider,
ReactNativeNames.ProviderLocalName,
ObjectCreationExpression(
ReactTypes.ReactConstantProvider,
IdentifierName(ReactNativeNames.WriterLocalName))),
InvocationStatement(
MemberAccessExpression(ReactNativeNames.Module, Identifier(constantProvider.Method.Name)),
IdentifierName(ReactNativeNames.ProviderLocalName))),
expressionBody: null
)
);
}
internal StatementSyntax AddGetConstants(ReactGetConstants getConstants)
{
// generates:
// moduleBuilder.AddConstantProvider( (IJSValueWriter writer) => {
// var provider = new ReactConstantProvider(writer);
// var constants = module.GetConstantsName();
// provider.WriteProperties(constants);
// });
return InvocationStatement(
MemberAccessExpression(ReactNativeNames.ModuleBuilder, ReactNativeNames.AddConstantProvider),
ParenthesizedLambdaExpression(
parameterList: ParameterList(
Parameter(ReactNativeNames.WriterLocalName)
.WithType(ReactTypes.IJSValueWriter.ToTypeSyntax())),
block: Block(
LocalDeclarationStatement(
ReactTypes.ReactConstantProvider,
ReactNativeNames.ProviderLocalName,
ObjectCreationExpression(
ReactTypes.ReactConstantProvider,
IdentifierName(ReactNativeNames.WriterLocalName))),
LocalDeclarationStatement(
ReactNativeNames.ConstantsLocalName,
InvocationExpression(
MemberAccessExpression(ReactNativeNames.Module, Identifier(getConstants.Method.Name)))),
InvocationStatement(
MemberAccessExpression(ReactNativeNames.ProviderLocalName, ReactNativeNames.WritePropertiesMethodName),
IdentifierName(ReactNativeNames.ConstantsLocalName))),
expressionBody: null
)
);
}
internal StatementSyntax AddMethod(ReactMethod method)
{
var statements = new List<StatementSyntax>();
var parameters = method.Method.Parameters;
var args = new List<ExpressionSyntax>(parameters.Length);
List<ArgumentSyntax>? readArgsArguments = null;
if (method.EffectiveParameters.Count > 0)
{
readArgsArguments = new List<ArgumentSyntax>(method.EffectiveParameters.Count + 1) {
Argument(IdentifierName(ReactNativeNames.ReaderLocalName))
};
// generates output arguments:
// (..., out ArgType0 arg0, out ArgType1 arg1, ...);
readArgsArguments.AddRange(method.EffectiveParameters.Select((param, i) => Argument(
nameColon: null,
refKindKeyword: Token(SyntaxKind.OutKeyword),
expression: DeclarationExpression(
param.Type.ToTypeSyntax(),
SingleVariableDesignation(ReactNativeNames.ArgLocalNames[i])))));
// generates:
// (arg0, arg1, ... )
args.AddRange(method.EffectiveParameters.Select((_, i) => IdentifierName(ReactNativeNames.ArgLocalNames[i])));
}
switch (method.ReturnStyle)
{
case ReactMethod.MethodReturnStyle.Promise:
args.Add(ObjectCreationExpression(
SymbolEqualityComparer.Default.Equals(method.EffectiveReturnType, ReactTypes.SystemVoid)
? ReactTypes.ReactPromiseOfVoid
: ReactTypes.ReactPromise.Construct(method.EffectiveReturnType),
IdentifierName(ReactNativeNames.WriterLocalName),
IdentifierName(ReactNativeNames.ResolveLocalName),
IdentifierName(ReactNativeNames.RejectLocalName)
));
break;
case ReactMethod.MethodReturnStyle.Callback:
args.Add(GenerateCallbackInvocation(ReactNativeNames.ResolveLocalName, method.ResolveParameterCount));
break;
case ReactMethod.MethodReturnStyle.TwoCallbacks:
args.Add(GenerateCallbackInvocation(ReactNativeNames.ResolveLocalName, method.ResolveParameterCount));
args.Add(GenerateCallbackInvocation(ReactNativeNames.RejectLocalName, method.RejectParameterCount));
break;
}
// Generate reader call only if we have arguments to read.
if (readArgsArguments != null)
{
// generates:
// reader.ReadArgs( ... )
statements.Add(InvocationStatement(
MemberAccessExpression(ReactTypes.JSValueReader, ReactNativeNames.ReadArgsMethodName),
readArgsArguments));
}
var methodCall = InvocationStatement(
MemberAccessExpression(ReactNativeNames.Module, Identifier(method.Method.Name)),
args.ToArray()
);
if (method.Method.ReturnsVoid)
{
// generate:
// module.MyMethod(arg0, arg1, ...)
statements.Add(methodCall);
}
else
{
// generate:
// MyResult result = module.MyMethod(arg0, arg1, ...);
statements.Add(
LocalDeclarationStatement(
method.Method.ReturnType,
ReactNativeNames.ResultLocalName,
methodCall.Expression));
if (method.ReturnStyle == ReactMethod.MethodReturnStyle.Task)
{
// generate:
// ReactTaskExtension.ContinueWith(result, writer, resolve, reject);
statements.Add(InvocationStatement(
MemberAccessExpression(ReactTypes.ReactTaskExtensions, ReactNativeNames.ContinueWith),
IdentifierName(ReactNativeNames.ResultLocalName),
IdentifierName(ReactNativeNames.WriterLocalName),
IdentifierName(ReactNativeNames.ResolveLocalName),
IdentifierName(ReactNativeNames.RejectLocalName)
));
}
else
{
if (method.IsSynchronous)
{
// generate:
// writer.WriteValue(result);
var writeValue = InvocationExpression(
MemberAccessExpression(ReactTypes.JSValueWriter, ReactNativeNames.WriteValueMethodName),
IdentifierName(ReactNativeNames.WriterLocalName),
IdentifierName(ReactNativeNames.ResultLocalName));
statements.Add(ExpressionStatement(writeValue));
}
else
{
// generate:
// writer.WriteArgs(result);
var writeArgs = InvocationExpression(
MemberAccessExpression(ReactTypes.JSValueWriter, ReactNativeNames.WriteArgsMethodName),
IdentifierName(ReactNativeNames.WriterLocalName),
IdentifierName(ReactNativeNames.ResultLocalName));
// generate:
// resolve(.. writeargs ..);
statements.Add(
InvocationStatement(
IdentifierName(ReactNativeNames.ResolveLocalName),
writeArgs
));
}
}
}
if (method.IsSynchronous)
{
// generate:
// moduleBuilder.AddSyncMethod(
// "MyMethod",
// (
// IJSValueReader reader,
// IJSValueWriter writer) =>
// {
// .... statements
// }
return InvocationStatement(
MemberAccessExpression(ReactNativeNames.ModuleBuilder, ReactNativeNames.AddSyncMethod),
LiteralExpression(method.Name),
ParenthesizedLambdaExpression(
parameterList:
ParameterList(
Parameter(ReactNativeNames.ReaderLocalName)
.WithType(ReactTypes.IJSValueReader.ToTypeSyntax()),
Parameter(ReactNativeNames.WriterLocalName)
.WithType(ReactTypes.IJSValueWriter.ToTypeSyntax())),
block: Block(statements),
expressionBody: null));
}
else
{
// generate:
// moduleBuilder.AddMethod(
// "MyMethod",
// MethodReturnType.xxx,
// (
// IJSValueReader reader,
// IJSValueWriter writer,
// MethodResultCallback resolve,
// MethodResultCallback reject) =>
// {
// .... statements
// }
return InvocationStatement(
MemberAccessExpression(
ReactNativeNames.ModuleBuilder,
method.IsSynchronous ? ReactNativeNames.AddSyncMethod : ReactNativeNames.AddMethod),
LiteralExpression(method.Name),
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
ReactTypes.MethodReturnType.ToTypeSyntax(),
SyntaxFactory.IdentifierName(GetMethodReturnTypeFromStyle(method.ReturnStyle))),
ParenthesizedLambdaExpression(
parameterList:
ParameterList(
Parameter(ReactNativeNames.ReaderLocalName)
.WithType(ReactTypes.IJSValueReader.ToTypeSyntax()),
Parameter(ReactNativeNames.WriterLocalName)
.WithType(ReactTypes.IJSValueWriter.ToTypeSyntax()),
Parameter(ReactNativeNames.ResolveLocalName)
.WithType(ReactTypes.MethodResultCallback.ToTypeSyntax()),
Parameter(ReactNativeNames.RejectLocalName)
.WithType(ReactTypes.MethodResultCallback.ToTypeSyntax())),
block: Block(
statements),
expressionBody: null));
}
}
internal ExpressionSyntax GenerateCallbackInvocation(SyntaxToken callbackName, int parameterCount)
{
// generates:
// (value0, value1, ...) => resolve|reject(JSValueWriter.WriteArgs(writer, value0, value1))
return ParenthesizedLambdaExpression(
parameterList: ParameterList(
SeparatedList(
ReactNativeNames.ValueLocalNames.Take(parameterCount).Select(name => Parameter(name)).ToArray())),
block: null,
expressionBody: InvocationExpression(
IdentifierName(callbackName),
InvocationExpression(
MemberAccessExpression(ReactTypes.JSValueWriter, ReactNativeNames.WriteArgsMethodName),
Enumerable.Concat(
Enumerable.Repeat(IdentifierName(ReactNativeNames.WriterLocalName), 1),
ReactNativeNames.ValueLocalNames.Take(parameterCount).Select(name => IdentifierName(name))))));
}
internal StatementSyntax AddEvent(ReactEvent evnt)
{
// generates:
// moduleBuilder.AddInitializer((IReactContext reactContext) =>
// module.MyEvent = (ArgType0 arg0, ArgType1 arg1, ...) => new ReactContext(reactContext).EmitJsEvent("eventEmitterName", "eventName", arg0, arg1, ...);
return InvocationStatement(
MemberAccessExpression(ReactNativeNames.ModuleBuilder, ReactNativeNames.AddInitializer),
ParenthesizedLambdaExpression(
parameterList: ParameterList(
Parameter(ReactNativeNames.ReactContextLocalName).WithType(ReactTypes.IReactContext.ToTypeSyntax())),
block: null,
expressionBody: GenerateCallback(evnt, ReactNativeNames.EmitJSEventFunctionName)));
}
internal StatementSyntax AddFunction(ReactFunction function)
{
// generates:
// moduleBuilder.AddInitializer((IReactContext reactContext) =>
// module.MyEvent = (ArgType0 arg0, ArgType1 arg1, ...) => new ReactContext(reactContext).EmitJsFunction("moduleName", "eventName", arg0, arg1, ...);
return InvocationStatement(
MemberAccessExpression(ReactNativeNames.ModuleBuilder, ReactNativeNames.AddInitializer),
ParenthesizedLambdaExpression(
parameterList: ParameterList(
Parameter(ReactNativeNames.ReactContextLocalName).WithType(ReactTypes.IReactContext.ToTypeSyntax())),
block: null,
expressionBody: GenerateCallback(function, ReactNativeNames.CallJSFunctionFunctionName)));
}
internal StatementSyntax AddInitializer(ReactInitializer initializer)
{
// generates:
// moduleBuilder.AddInitializer((IReactContext reactContext) => module.initializerMethod(new ReactContext(reactContext)));
return InvocationStatement(
MemberAccessExpression(ReactNativeNames.ModuleBuilder, ReactNativeNames.AddInitializer),
ParenthesizedLambdaExpression(
parameterList: ParameterList(
Parameter(ReactNativeNames.ReactContextLocalName).WithType(ReactTypes.IReactContext.ToTypeSyntax())),
block: null,
expressionBody: InvocationExpression(
MemberAccessExpression(ReactNativeNames.Module, Identifier(initializer.Method.Name)),
ObjectCreationExpression(ReactTypes.ReactContext, IdentifierName(ReactNativeNames.ReactContextLocalName))
)));
}
private ExpressionSyntax GenerateCallback(ReactCallback callback, SyntaxToken contextCallbackMethod)
{
var lambdaParams = new List<ParameterSyntax>(callback.CallbackParameters.Length);
var arguments = new List<ArgumentSyntax>(callback.CallbackParameters.Length);
arguments.Add(Argument(LiteralExpression(callback.CallbackContextName)));
arguments.Add(Argument(LiteralExpression(callback.Name)));
for (int i = 0; i < callback.CallbackParameters.Length; i++)
{
var paramType = callback.CallbackParameters[i].Type;
var identifierName = "arg" + i.ToString(CultureInfo.InvariantCulture);
lambdaParams.Add(Parameter(Identifier(identifierName)).WithType(paramType.ToTypeSyntax()));
arguments.Add(Argument(IdentifierName(identifierName)));
}
// generates:
// module.<callbackName> = (ArgType0 arg0, ArgType1 arg0, ...) =>
// new ReactContext(reactContext).<contextCallbackMethod>(
// eventEmitterName,
// eventName,
// arg0, arg1, ...
// )
return
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
MemberAccessExpression(ReactNativeNames.Module, Identifier(callback.Symbol.Name)),
ParenthesizedLambdaExpression(
parameterList: ParameterList(lambdaParams.ToArray()),
block: null,
expressionBody: InvocationExpression(
MemberAccessExpression(
ObjectCreationExpression(ReactTypes.ReactContext, IdentifierName(ReactNativeNames.ReactContextLocalName)),
contextCallbackMethod),
arguments
)));
}
private string GetMethodReturnTypeFromStyle(ReactMethod.MethodReturnStyle returnStyle)
{
switch (returnStyle)
{
case ReactMethod.MethodReturnStyle.Void:
return "Void";
case ReactMethod.MethodReturnStyle.Task:
case ReactMethod.MethodReturnStyle.Promise:
return "Promise";
case ReactMethod.MethodReturnStyle.Callback:
case ReactMethod.MethodReturnStyle.ReturnValue:
return "Callback";
case ReactMethod.MethodReturnStyle.TwoCallbacks:
return "TwoCallbacks";
default:
throw new InvalidOperationException("Unexpected ReturnStyle");
}
}
}
}