< Summary

Information
Class: Elsa.Dsl.ElsaScript.Compiler.ElsaScriptCompiler
Assembly: Elsa.Dsl.ElsaScript
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Dsl.ElsaScript/Compiler/ElsaScriptCompiler.cs
Line coverage
69%
Covered lines: 198
Uncovered lines: 86
Coverable lines: 284
Total lines: 600
Line coverage: 69.7%
Branch coverage
68%
Covered branches: 103
Total branches: 150
Branch coverage: 68.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Dsl.ElsaScript/Compiler/ElsaScriptCompiler.cs

#LineLine coverage
 1using Elsa.Dsl.ElsaScript.Ast;
 2using Elsa.Dsl.ElsaScript.Contracts;
 3using Elsa.Dsl.ElsaScript.Helpers;
 4using Elsa.Expressions.Models;
 5using Elsa.Workflows;
 6using Elsa.Workflows.Activities;
 7using Elsa.Workflows.Activities.Flowchart.Models;
 8using Elsa.Workflows.Memory;
 9using Elsa.Workflows.Models;
 10
 11namespace Elsa.Dsl.ElsaScript.Compiler;
 12
 13/// <summary>
 14/// Compiles an ElsaScript AST into Elsa workflows.
 15/// </summary>
 27716public class ElsaScriptCompiler(IActivityRegistryLookupService activityRegistryLookupService, IElsaScriptParser parser) 
 17{
 118    private static readonly Dictionary<string, string> LanguageMappings = new(StringComparer.OrdinalIgnoreCase)
 119    {
 120        ["js"] = "JavaScript",
 121        ["cs"] = "CSharp",
 122        ["py"] = "Python",
 123        ["liquid"] = "Liquid"
 124    };
 25
 27726    private string _defaultExpressionLanguage = "JavaScript";
 27727    private readonly Dictionary<string, Variable> _variables = new();
 28
 29    /// <inheritdoc />
 30    public async Task<Workflow> CompileAsync(string source, CancellationToken cancellationToken = default)
 31    {
 1232        var programNode = parser.Parse(source);
 1233        return await CompileAsync(programNode, cancellationToken);
 1234    }
 35
 36    /// <inheritdoc />
 37    public async Task<Workflow> CompileAsync(ProgramNode programNode, CancellationToken cancellationToken = default)
 38    {
 39        // Get the single workflow (enforced by parser)
 1240        var workflowNode = programNode.Workflows.First();
 41
 42        // Merge global use statements with workflow-level ones
 43        // Create a temporary workflow node with merged use statements
 1244        var mergedWorkflowNode = new WorkflowNode
 1245        {
 1246            Id = workflowNode.Id,
 1247            Metadata = workflowNode.Metadata,
 1248            UseStatements = [..programNode.GlobalUseStatements, ..workflowNode.UseStatements],
 1249            Body = workflowNode.Body
 1250        };
 51
 1252        return await CompileWorkflowNodeAsync(mergedWorkflowNode, cancellationToken);
 1253    }
 54
 55    private async Task<Workflow> CompileWorkflowNodeAsync(WorkflowNode workflowNode, CancellationToken cancellationToken
 56    {
 1257        _variables.Clear();
 1258        _defaultExpressionLanguage = "JavaScript";
 59
 60        // Process use statements (workflow-level overrides global)
 4261        foreach (var useNode in workflowNode.UseStatements)
 62        {
 963            if (useNode.Type == UseType.Expressions)
 64            {
 765                _defaultExpressionLanguage = MapLanguageName(useNode.Value);
 66            }
 67        }
 68
 69        // Compile body statements
 1270        var activities = new List<IActivity>();
 5871        foreach (var statement in workflowNode.Body)
 72        {
 1773            var activity = await CompileStatementAsync(statement, cancellationToken);
 1774            if (activity != null)
 75            {
 1376                activities.Add(activity);
 77            }
 78        }
 79
 80        // Create the root activity (Sequence containing all statements)
 1281        var root = activities.Count == 1
 1282            ? activities[0]
 1283            : new Sequence
 1284            {
 1285                Activities = activities
 1286            };
 87
 88        // Extract metadata with defaults
 1289        var definitionId = GetMetadataValue<string>(workflowNode.Metadata, "DefinitionId") ?? workflowNode.Id;
 1290        var displayName = GetMetadataValue<string>(workflowNode.Metadata, "DisplayName") ?? workflowNode.Id;
 1291        var description = GetMetadataValue<string>(workflowNode.Metadata, "Description") ?? string.Empty;
 1292        var definitionVersionId = GetMetadataValue<string>(workflowNode.Metadata, "DefinitionVersionId") ?? $"{definitio
 1293        var version = GetMetadataValueOrDefault(workflowNode.Metadata, "Version", 1);
 1294        var usableAsActivity = GetMetadataValue<bool?>(workflowNode.Metadata, "UsableAsActivity");
 95
 96        // Create the workflow
 1297        var workflow = new Workflow
 1298        {
 1299            Name = displayName,
 12100            Identity = new WorkflowIdentity(definitionId, version, definitionVersionId, null),
 12101            WorkflowMetadata = new(displayName, description, ToolVersion: new("3.6.0")),
 12102            Root = root,
 12103            Variables = _variables.Values.ToList(),
 12104            Options = new()
 12105            {
 12106                UsableAsActivity = usableAsActivity
 12107            }
 12108        };
 109
 12110        return workflow;
 12111    }
 112
 113    private T? GetMetadataValue<T>(Dictionary<string, object> metadata, string key) =>
 60114        metadata.TryGetValue(key, out var value) ? ConvertValue<T>(value, default) : default;
 115
 116    private T GetMetadataValueOrDefault<T>(Dictionary<string, object> metadata, string key, T defaultValue) =>
 12117        metadata.TryGetValue(key, out var value) ? ConvertValue(value, defaultValue) : defaultValue;
 118
 119    private static T? ConvertValue<T>(object value, T? defaultValue)
 120    {
 121        // Handle direct type match
 6122        if (value is T typedValue)
 5123            return typedValue;
 124
 125        // Try to convert
 126        try
 127        {
 1128            var targetType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
 1129            return (T)Convert.ChangeType(value, targetType);
 130        }
 0131        catch (Exception)
 132        {
 133            // Return default value if conversion fails
 0134            return defaultValue;
 135        }
 1136    }
 137
 138    private async Task<IActivity?> CompileStatementAsync(StatementNode statement, CancellationToken cancellationToken = 
 139    {
 24140        return statement switch
 24141        {
 4142            VariableDeclarationNode varDecl => CompileVariableDeclaration(varDecl),
 13143            ActivityInvocationNode actInv => await CompileActivityInvocationAsync(actInv, cancellationToken),
 1144            BlockNode block => await CompileBlockAsync(block, cancellationToken),
 0145            IfNode ifNode => await CompileIfAsync(ifNode, cancellationToken),
 0146            ForEachNode forEach => await CompileForEachAsync(forEach, cancellationToken),
 2147            ForNode forNode => await CompileForAsync(forNode, cancellationToken),
 0148            WhileNode whileNode => await CompileWhileAsync(whileNode, cancellationToken),
 0149            SwitchNode switchNode => await CompileSwitchAsync(switchNode, cancellationToken),
 3150            FlowchartNode flowchart => await CompileFlowchartAsync(flowchart, cancellationToken),
 1151            ListenNode listen => await CompileListenAsync(listen, cancellationToken),
 0152            _ => throw new NotSupportedException($"Statement type {statement.GetType().Name} is not supported")
 24153        };
 24154    }
 155
 156    private IActivity? CompileVariableDeclaration(VariableDeclarationNode varDecl)
 157    {
 158        // Create and register the variable
 4159        var initialValue = varDecl.Value != null ? EvaluateConstantExpression(varDecl.Value) : null;
 4160        var variable = new Variable(varDecl.Name, initialValue);
 4161        _variables[varDecl.Name] = variable;
 162
 163        // Variable declarations don't produce activities themselves
 4164        return null;
 165    }
 166
 167    private async Task<IActivity> CompileActivityInvocationAsync(ActivityInvocationNode actInv, CancellationToken cancel
 168    {
 169        // Try to find the activity type by name - try several strategies
 14170        var activityDescriptor = await activityRegistryLookupService.FindAsync(actInv.ActivityName);
 171
 172        // If not found, try with "Elsa." prefix
 14173        if (activityDescriptor == null)
 174        {
 14175            activityDescriptor = await activityRegistryLookupService.FindAsync($"Elsa.{actInv.ActivityName}");
 176        }
 177
 178        // If still not found, search by descriptor name
 14179        if (activityDescriptor == null)
 180        {
 0181            activityDescriptor = await activityRegistryLookupService.FindAsync(d => d.Name == actInv.ActivityName);
 182        }
 183
 14184        if (activityDescriptor == null)
 185        {
 0186            throw new InvalidOperationException($"Activity '{actInv.ActivityName}' not found in registry");
 187        }
 188
 14189        var activityType = activityDescriptor.ClrType;
 190
 191        // Separate named and positional arguments
 192        var namedArgs = actInv.Arguments.Where(a => a.Name != null).ToList();
 193        var positionalArgs = actInv.Arguments.Where(a => a.Name == null).ToList();
 194
 195        IActivity activity;
 196
 197        // If we have positional arguments, try to find a matching constructor
 14198        if (positionalArgs.Any())
 199        {
 12200            activity = InstantiateActivityUsingConstructor(activityType, positionalArgs);
 201        }
 202        else
 203        {
 204            // No positional arguments, use default constructor
 2205            var activityConstructorContext = new ActivityConstructorContext(activityDescriptor, ActivityActivator.Create
 2206            activity = activityDescriptor.Constructor(activityConstructorContext);
 207        }
 208
 209        // Set named argument properties
 28210        foreach (var arg in namedArgs)
 211        {
 0212            var property = activityType.GetProperty(arg.Name!);
 213
 0214            if (property != null)
 215            {
 0216                var value = CompileExpression(arg.Value, property.PropertyType);
 0217                property.SetValue(activity, value);
 218            }
 219            else
 220            {
 0221                throw new InvalidOperationException($"Property '{arg.Name}' not found on activity type '{activityType.Na
 222            }
 223        }
 224
 14225        return activity;
 14226    }
 227
 228    private async Task<IActivity> CompileBlockAsync(BlockNode block, CancellationToken cancellationToken = default)
 229    {
 1230        var activities = new List<IActivity>();
 231
 6232        foreach (var statement in block.Statements)
 233        {
 2234            var activity = await CompileStatementAsync(statement, cancellationToken);
 2235            if (activity != null)
 236            {
 2237                activities.Add(activity);
 238            }
 239        }
 240
 1241        return new Sequence
 1242        {
 1243            Activities = activities
 1244        };
 1245    }
 246
 247    private async Task<IActivity> CompileIfAsync(IfNode ifNode, CancellationToken cancellationToken = default)
 248    {
 0249        var condition = CompileExpressionAsInput<bool>(ifNode.Condition);
 0250        var thenActivity = await CompileStatementAsync(ifNode.Then, cancellationToken);
 0251        var elseActivity = ifNode.Else != null ? await CompileStatementAsync(ifNode.Else, cancellationToken) : null;
 252
 0253        return new If(condition)
 0254        {
 0255            Then = thenActivity,
 0256            Else = elseActivity
 0257        };
 0258    }
 259
 260    private async Task<IActivity> CompileForEachAsync(ForEachNode forEach, CancellationToken cancellationToken = default
 261    {
 262        Variable loopVariable;
 263
 0264        if (forEach.DeclaresVariable)
 265        {
 266            // Create a new loop variable
 0267            loopVariable = new Variable<object>(forEach.VariableName, null!);
 0268            _variables[forEach.VariableName] = loopVariable;
 269        }
 270        else
 271        {
 272            // Reuse existing variable
 0273            if (!_variables.TryGetValue(forEach.VariableName, out loopVariable!))
 274            {
 0275                throw new InvalidOperationException($"Variable '{forEach.VariableName}' is not declared. Use 'var {forEa
 276            }
 277        }
 278
 0279        var items = CompileExpressionAsInput<ICollection<object>>(forEach.Collection);
 0280        var body = await CompileStatementAsync(forEach.Body, cancellationToken);
 281
 0282        var forEachActivity = new ForEach<object>(items)
 0283        {
 0284            CurrentValue = new(loopVariable),
 0285            Body = body
 0286        };
 287
 0288        return forEachActivity;
 0289    }
 290
 291    private async Task<IActivity> CompileForAsync(ForNode forNode, CancellationToken cancellationToken = default)
 292    {
 293        Variable loopVariable;
 294
 2295        if (forNode.DeclaresVariable)
 296        {
 297            // Create a new loop variable
 2298            loopVariable = new Variable<int>(forNode.VariableName, 0);
 2299            _variables[forNode.VariableName] = loopVariable;
 300        }
 301        else
 302        {
 303            // Reuse existing variable
 0304            if (!_variables.TryGetValue(forNode.VariableName, out loopVariable!))
 305            {
 0306                throw new InvalidOperationException($"Variable '{forNode.VariableName}' is not declared. Use 'var {forNo
 307            }
 308        }
 309
 2310        var start = CompileExpressionAsInput<int>(forNode.Start);
 2311        var end = CompileExpressionAsInput<int>(forNode.End);
 2312        var step = CompileExpressionAsInput<int>(forNode.Step);
 2313        var body = await CompileStatementAsync(forNode.Body, cancellationToken);
 314
 2315        var forActivity = new For
 2316        {
 2317            Start = start,
 2318            End = end,
 2319            Step = step,
 2320            OuterBoundInclusive = new Input<bool>(forNode.IsInclusive),
 2321            CurrentValue = new Output<object?>(loopVariable),
 2322            Body = body
 2323        };
 324
 2325        return forActivity;
 2326    }
 327
 328    private async Task<IActivity> CompileWhileAsync(WhileNode whileNode, CancellationToken cancellationToken = default)
 329    {
 0330        var condition = CompileExpressionAsInput<bool>(whileNode.Condition);
 0331        var body = await CompileStatementAsync(whileNode.Body, cancellationToken);
 332
 0333        return new While(condition)
 0334        {
 0335            Body = body
 0336        };
 0337    }
 338
 339    private async Task<IActivity> CompileSwitchAsync(SwitchNode switchNode, CancellationToken cancellationToken = defaul
 340    {
 0341        var cases = new List<SwitchCase>();
 342
 0343        foreach (var caseNode in switchNode.Cases)
 344        {
 0345            var caseExpression = CompileExpressionAsExpression(caseNode.Value);
 0346            var caseBody = await CompileStatementAsync(caseNode.Body, cancellationToken);
 0347            cases.Add(new("Case", caseExpression, caseBody!));
 0348        }
 349
 0350        var defaultActivity = switchNode.Default != null ? await CompileStatementAsync(switchNode.Default, cancellationT
 351
 0352        return new Switch
 0353        {
 0354            Cases = cases,
 0355            Default = defaultActivity
 0356        };
 0357    }
 358
 359    private async Task<IActivity> CompileFlowchartAsync(FlowchartNode flowchart, CancellationToken cancellationToken = d
 360    {
 361        // Register flowchart-scoped variables
 6362        foreach (var varDecl in flowchart.Variables)
 363        {
 0364            CompileVariableDeclaration(varDecl);
 365        }
 366
 367        // Compile all labeled activities and build a label-to-activity map
 3368        var labelToActivity = new Dictionary<string, IActivity>();
 12369        foreach (var labeledNode in flowchart.Activities)
 370        {
 3371            var activity = await CompileStatementAsync(labeledNode.Activity, cancellationToken);
 3372            if (activity != null)
 373            {
 3374                labelToActivity[labeledNode.Label] = activity;
 375            }
 3376        }
 377
 378        // Create connections
 3379        var connections = new List<Connection>();
 8380        foreach (var connNode in flowchart.Connections)
 381        {
 1382            if (!labelToActivity.TryGetValue(connNode.Source, out var sourceActivity))
 0383                throw new InvalidOperationException($"Source label '{connNode.Source}' not found in flowchart");
 384
 1385            if (!labelToActivity.TryGetValue(connNode.Target, out var targetActivity))
 0386                throw new InvalidOperationException($"Target label '{connNode.Target}' not found in flowchart");
 387
 1388            var source = new Endpoint(sourceActivity, connNode.Outcome);
 1389            var target = new Endpoint(targetActivity);
 1390            connections.Add(new Connection(source, target));
 391        }
 392
 393        // Create flowchart activity
 3394        var flowchartActivity = new Workflows.Activities.Flowchart.Activities.Flowchart
 3395        {
 3396            Activities = labelToActivity.Values.ToList(),
 3397            Connections = connections
 3398        };
 399
 400        // Set entry point if specified
 3401        if (!string.IsNullOrEmpty(flowchart.EntryPoint))
 402        {
 2403            if (!labelToActivity.TryGetValue(flowchart.EntryPoint, out var startActivity))
 0404                throw new InvalidOperationException($"Entry point label '{flowchart.EntryPoint}' not found in flowchart"
 405
 2406            flowchartActivity.Start = startActivity;
 407        }
 408
 3409        return flowchartActivity;
 3410    }
 411
 412    private async Task<IActivity> CompileListenAsync(ListenNode listen, CancellationToken cancellationToken = default)
 413    {
 414        // Listen is just a regular activity invocation that can start a workflow
 1415        var activity = await CompileActivityInvocationAsync(listen.Activity, cancellationToken);
 416
 417        // Try to set CanStartWorkflow if the activity supports it
 1418        var canStartWorkflowProp = activity.GetType().GetProperty("CanStartWorkflow");
 1419        if (canStartWorkflowProp != null && canStartWorkflowProp.PropertyType == typeof(bool))
 420        {
 1421            canStartWorkflowProp.SetValue(activity, true);
 422        }
 423
 1424        return activity;
 1425    }
 426
 427    private Input<T> CompileExpressionAsInput<T>(ExpressionNode exprNode)
 428    {
 18429        if (exprNode is LiteralNode literal)
 430        {
 12431            return new(new Literal(literal.Value!));
 432        }
 433
 6434        if (exprNode is IdentifierNode identifier)
 435        {
 436            // Reference to a variable
 1437            if (_variables.TryGetValue(identifier.Name, out var variable))
 438            {
 1439                return new(variable);
 440            }
 441
 442            // If not found, treat as a literal
 0443            return new(new Literal<T>(default!));
 444        }
 445
 5446        if (exprNode is ElsaExpressionNode elsaExpr)
 447        {
 5448            var language = elsaExpr.Language != null ? MapLanguageName(elsaExpr.Language) : _defaultExpressionLanguage;
 5449            var expression = new Expression(language, elsaExpr.Expression);
 5450            return new(expression);
 451        }
 452
 0453        if (exprNode is ArrayLiteralNode arrayLiteral)
 454        {
 455            // For array literals, evaluate to a constant array if all elements are literals
 0456            var elements = arrayLiteral.Elements.Select(EvaluateConstantExpression).ToArray();
 0457            return new((T)(object)elements);
 458        }
 459
 0460        throw new NotSupportedException($"Expression type {exprNode.GetType().Name} is not supported");
 461    }
 462
 463    private Expression CompileExpressionAsExpression(ExpressionNode exprNode)
 464    {
 0465        if (exprNode is LiteralNode literal)
 466        {
 0467            return Expression.LiteralExpression(literal.Value);
 468        }
 469
 0470        if (exprNode is ElsaExpressionNode elsaExpr)
 471        {
 0472            var language = elsaExpr.Language != null ? MapLanguageName(elsaExpr.Language) : _defaultExpressionLanguage;
 0473            return new(language, elsaExpr.Expression);
 474        }
 475
 0476        throw new NotSupportedException($"Expression type {exprNode.GetType().Name} is not supported as Expression");
 477    }
 478
 479    private object CompileExpression(ExpressionNode exprNode, Type targetType)
 480    {
 481        // Check if targetType is already Input<T>
 482        Type innerType;
 12483        if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Input<>))
 484        {
 485            // Extract the T from Input<T>
 12486            innerType = targetType.GetGenericArguments()[0];
 487        }
 488        else
 489        {
 0490            innerType = targetType;
 491        }
 492
 493        // Use reflection to call CompileExpressionAsInput<T>
 12494        var method = GetType().GetMethod(nameof(CompileExpressionAsInput),
 12495            System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
 12496        var genericMethod = method!.MakeGenericMethod(innerType);
 497
 12498        return genericMethod.Invoke(this, [exprNode])!;
 499    }
 500
 501    private object? EvaluateConstantExpression(ExpressionNode exprNode)
 502    {
 4503        if (exprNode is LiteralNode literal)
 504        {
 4505            return literal.Value;
 506        }
 507
 0508        if (exprNode is ArrayLiteralNode arrayLiteral)
 509        {
 0510            return arrayLiteral.Elements.Select(EvaluateConstantExpression).ToArray();
 511        }
 512
 513        // For non-constant expressions, return null
 0514        return null;
 515    }
 516
 517    private IActivity InstantiateActivityUsingConstructor(Type activityType, List<ArgumentNode> positionalArgs)
 518    {
 519        // Get all public constructors
 12520        var constructors = activityType.GetConstructors(System.Reflection.BindingFlags.Public | System.Reflection.Bindin
 521
 522        // Filter constructors that:
 523        // 1. Have the same number of required Input<T> parameters as positional arguments (excluding optional params)
 524        // 2. All non-optional parameters are Input<T> types
 12525        var matchingConstructors = new List<(System.Reflection.ConstructorInfo ctor, System.Reflection.ParameterInfo[] i
 526
 176527        foreach (var ctor in constructors)
 528        {
 76529            var parameters = ctor.GetParameters();
 530
 531            // Filter to only Input<T> parameters that are not optional (don't have default values or CallerMemberName a
 76532            var inputParams = parameters.Where(p =>
 228533                p.ParameterType.IsGenericType &&
 228534                p.ParameterType.GetGenericTypeDefinition() == typeof(Input<>) &&
 228535                !p.IsOptional &&
 228536                !p.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CallerFilePathAttribute), false).Any() &&
 228537                !p.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CallerLineNumberAttribute), false).Any() &
 228538                !p.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CallerMemberNameAttribute), false).Any()
 76539            ).ToArray();
 540
 541            // Check if the number of required Input<T> params matches our positional args
 76542            if (inputParams.Length == positionalArgs.Count)
 543            {
 12544                matchingConstructors.Add((ctor, inputParams));
 545            }
 546        }
 547
 12548        if (!matchingConstructors.Any())
 549        {
 0550            throw new InvalidOperationException(
 0551                $"No matching constructor found for activity type '{activityType.Name}' with {positionalArgs.Count} posi
 0552                $"Constructors must have Input<T> parameters matching the number of positional arguments.");
 553        }
 554
 12555        if (matchingConstructors.Count > 1)
 556        {
 0557            throw new InvalidOperationException(
 0558                $"Multiple matching constructors found for activity type '{activityType.Name}' with {positionalArgs.Coun
 0559                $"Please use named arguments to disambiguate.");
 560        }
 561
 12562        var (selectedCtor, selectedInputParams) = matchingConstructors[0];
 563
 564        // Build the constructor arguments
 12565        var ctorArgs = new List<object?>();
 12566        var allParams = selectedCtor.GetParameters();
 567
 96568        foreach (var param in allParams)
 569        {
 570            // Check if this is one of our Input<T> parameters
 36571            var inputParamIndex = Array.IndexOf(selectedInputParams, param);
 572
 36573            if (inputParamIndex >= 0)
 574            {
 575                // This is an Input<T> parameter - compile the corresponding positional argument
 12576                var arg = positionalArgs[inputParamIndex];
 12577                var value = CompileExpression(arg.Value, param.ParameterType);
 12578                ctorArgs.Add(value);
 579            }
 24580            else if (param.IsOptional)
 581            {
 582                // This is an optional parameter (like CallerFilePath) - use its default value
 24583                ctorArgs.Add(param.DefaultValue);
 584            }
 585            else
 586            {
 587                // This shouldn't happen if our filtering is correct
 0588                throw new InvalidOperationException(
 0589                    $"Unexpected non-optional, non-Input<T> parameter '{param.Name}' in constructor for '{activityType.N
 590            }
 591        }
 592
 593        // Instantiate the activity using the constructor
 12594        var activity = (IActivity)selectedCtor.Invoke(ctorArgs.ToArray());
 12595        return activity;
 596    }
 597
 598    private static string MapLanguageName(string dslLanguage) =>
 12599        LanguageMappings.TryGetValue(dslLanguage, out var mapped) ? mapped : dslLanguage;
 600}