< 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: 603
Line coverage: 69.7%
Branch coverage
68%
Covered branches: 104
Total branches: 152
Branch coverage: 68.4%
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 System.Diagnostics.CodeAnalysis;
 2using Elsa.Dsl.ElsaScript.Ast;
 3using Elsa.Dsl.ElsaScript.Contracts;
 4using Elsa.Dsl.ElsaScript.Helpers;
 5using Elsa.Expressions.Models;
 6using Elsa.Workflows;
 7using Elsa.Workflows.Activities;
 8using Elsa.Workflows.Activities.Flowchart.Models;
 9using Elsa.Workflows.Memory;
 10using Elsa.Workflows.Models;
 11
 12namespace Elsa.Dsl.ElsaScript.Compiler;
 13
 14/// <summary>
 15/// Compiles an ElsaScript AST into Elsa workflows.
 16/// </summary>
 36417public class ElsaScriptCompiler(IActivityRegistryLookupService activityRegistryLookupService, IElsaScriptParser parser) 
 18{
 119    private static readonly Dictionary<string, string> LanguageMappings = new(StringComparer.OrdinalIgnoreCase)
 120    {
 121        ["js"] = "JavaScript",
 122        ["cs"] = "CSharp",
 123        ["py"] = "Python",
 124        ["liquid"] = "Liquid"
 125    };
 26
 36427    private string _defaultExpressionLanguage = "JavaScript";
 36428    private readonly Dictionary<string, Variable> _variables = new();
 29
 30    /// <inheritdoc />
 31    public async Task<Workflow> CompileAsync(string source, CancellationToken cancellationToken = default)
 32    {
 1233        var programNode = parser.Parse(source);
 1234        return await CompileAsync(programNode, cancellationToken);
 1235    }
 36
 37    /// <inheritdoc />
 38    public async Task<Workflow> CompileAsync(ProgramNode programNode, CancellationToken cancellationToken = default)
 39    {
 40        // Get the single workflow (enforced by parser)
 1241        var workflowNode = programNode.Workflows.First();
 42
 43        // Merge global use statements with workflow-level ones
 44        // Create a temporary workflow node with merged use statements
 1245        var mergedWorkflowNode = new WorkflowNode
 1246        {
 1247            Id = workflowNode.Id,
 1248            Metadata = workflowNode.Metadata,
 1249            UseStatements = [..programNode.GlobalUseStatements, ..workflowNode.UseStatements],
 1250            Body = workflowNode.Body
 1251        };
 52
 1253        return await CompileWorkflowNodeAsync(mergedWorkflowNode, cancellationToken);
 1254    }
 55
 56    private async Task<Workflow> CompileWorkflowNodeAsync(WorkflowNode workflowNode, CancellationToken cancellationToken
 57    {
 1258        _variables.Clear();
 1259        _defaultExpressionLanguage = "JavaScript";
 60
 61        // Process use statements (workflow-level overrides global)
 4262        foreach (var useNode in workflowNode.UseStatements)
 63        {
 964            if (useNode.Type == UseType.Expressions)
 65            {
 766                _defaultExpressionLanguage = MapLanguageName(useNode.Value);
 67            }
 68        }
 69
 70        // Compile body statements
 1271        var activities = new List<IActivity>();
 5872        foreach (var statement in workflowNode.Body)
 73        {
 1774            var activity = await CompileStatementAsync(statement, cancellationToken);
 1775            if (activity != null)
 76            {
 1377                activities.Add(activity);
 78            }
 79        }
 80
 81        // Create the root activity (Sequence containing all statements)
 1282        var root = activities.Count == 1
 1283            ? activities[0]
 1284            : new Sequence
 1285            {
 1286                Activities = activities
 1287            };
 88
 89        // Extract metadata with defaults
 1290        var definitionId = GetMetadataValue<string>(workflowNode.Metadata, "DefinitionId") ?? workflowNode.Id;
 1291        var displayName = GetMetadataValue<string>(workflowNode.Metadata, "DisplayName") ?? workflowNode.Id;
 1292        var description = GetMetadataValue<string>(workflowNode.Metadata, "Description") ?? string.Empty;
 1293        var definitionVersionId = GetMetadataValue<string>(workflowNode.Metadata, "DefinitionVersionId") ?? $"{definitio
 1294        var version = GetMetadataValueOrDefault(workflowNode.Metadata, "Version", 1);
 1295        var usableAsActivity = GetMetadataValue<bool?>(workflowNode.Metadata, "UsableAsActivity");
 96
 97        // Create the workflow
 1298        var workflow = new Workflow
 1299        {
 12100            Name = displayName,
 12101            Identity = new WorkflowIdentity(definitionId, version, definitionVersionId, null),
 12102            WorkflowMetadata = new(displayName, description, ToolVersion: new("3.6.0")),
 12103            Root = root,
 12104            Variables = _variables.Values.ToList(),
 12105            Options = new()
 12106            {
 12107                UsableAsActivity = usableAsActivity
 12108            }
 12109        };
 110
 12111        return workflow;
 12112    }
 113
 114    private T? GetMetadataValue<T>(Dictionary<string, object> metadata, string key) =>
 60115        metadata.TryGetValue(key, out var value) ? ConvertValue<T>(value, default) : default;
 116
 117    private T GetMetadataValueOrDefault<T>(Dictionary<string, object> metadata, string key, T defaultValue) =>
 12118        metadata.TryGetValue(key, out var value) ? ConvertValue(value, defaultValue) ?? defaultValue : defaultValue;
 119
 120    private static T? ConvertValue<T>(object value, T? defaultValue)
 121    {
 122        // Handle direct type match
 6123        if (value is T typedValue)
 5124            return typedValue;
 125
 126        // Try to convert
 127        try
 128        {
 1129            var targetType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
 1130            return (T)Convert.ChangeType(value, targetType);
 131        }
 0132        catch (Exception)
 133        {
 134            // Return default value if conversion fails
 0135            return defaultValue;
 136        }
 1137    }
 138
 139    private async Task<IActivity?> CompileStatementAsync(StatementNode statement, CancellationToken cancellationToken = 
 140    {
 24141        return statement switch
 24142        {
 4143            VariableDeclarationNode varDecl => CompileVariableDeclaration(varDecl),
 13144            ActivityInvocationNode actInv => await CompileActivityInvocationAsync(actInv, cancellationToken),
 1145            BlockNode block => await CompileBlockAsync(block, cancellationToken),
 0146            IfNode ifNode => await CompileIfAsync(ifNode, cancellationToken),
 0147            ForEachNode forEach => await CompileForEachAsync(forEach, cancellationToken),
 2148            ForNode forNode => await CompileForAsync(forNode, cancellationToken),
 0149            WhileNode whileNode => await CompileWhileAsync(whileNode, cancellationToken),
 0150            SwitchNode switchNode => await CompileSwitchAsync(switchNode, cancellationToken),
 3151            FlowchartNode flowchart => await CompileFlowchartAsync(flowchart, cancellationToken),
 1152            ListenNode listen => await CompileListenAsync(listen, cancellationToken),
 0153            _ => throw new NotSupportedException($"Statement type {statement.GetType().Name} is not supported")
 24154        };
 24155    }
 156
 157    private IActivity? CompileVariableDeclaration(VariableDeclarationNode varDecl)
 158    {
 159        // Create and register the variable
 4160        var initialValue = varDecl.Value != null ? EvaluateConstantExpression(varDecl.Value) : null;
 4161        var variable = new Variable(varDecl.Name, initialValue);
 4162        _variables[varDecl.Name] = variable;
 163
 164        // Variable declarations don't produce activities themselves
 4165        return null;
 166    }
 167
 168    private async Task<IActivity> CompileActivityInvocationAsync(ActivityInvocationNode actInv, CancellationToken cancel
 169    {
 170        // Try to find the activity type by name - try several strategies
 14171        var activityDescriptor = await activityRegistryLookupService.FindAsync(actInv.ActivityName);
 172
 173        // If not found, try with "Elsa." prefix
 14174        if (activityDescriptor == null)
 175        {
 14176            activityDescriptor = await activityRegistryLookupService.FindAsync($"Elsa.{actInv.ActivityName}");
 177        }
 178
 179        // If still not found, search by descriptor name
 14180        if (activityDescriptor == null)
 181        {
 0182            activityDescriptor = await activityRegistryLookupService.FindAsync(d => d.Name == actInv.ActivityName);
 183        }
 184
 14185        if (activityDescriptor == null)
 186        {
 0187            throw new InvalidOperationException($"Activity '{actInv.ActivityName}' not found in registry");
 188        }
 189
 14190        var activityType = activityDescriptor.ClrType;
 191
 192        // Separate named and positional arguments
 193        var namedArgs = actInv.Arguments.Where(a => a.Name != null).ToList();
 194        var positionalArgs = actInv.Arguments.Where(a => a.Name == null).ToList();
 195
 196        IActivity activity;
 197
 198        // If we have positional arguments, try to find a matching constructor
 14199        if (positionalArgs.Any())
 200        {
 12201            activity = InstantiateActivityUsingConstructor(activityType, positionalArgs);
 202        }
 203        else
 204        {
 205            // No positional arguments, use default constructor
 206            var activityConstructorContext = new ActivityConstructorContext(activityDescriptor, (t) => new(ActivityActiv
 2207            var activityConstructionResult = activityDescriptor.Constructor(activityConstructorContext);
 2208            activity = activityConstructionResult.Activity;
 209        }
 210
 211        // Set named argument properties
 28212        foreach (var arg in namedArgs)
 213        {
 0214            var property = activityType.GetProperty(arg.Name!);
 215
 0216            if (property != null)
 217            {
 0218                var value = CompileExpression(arg.Value, property.PropertyType);
 0219                property.SetValue(activity, value);
 220            }
 221            else
 222            {
 0223                throw new InvalidOperationException($"Property '{arg.Name}' not found on activity type '{activityType.Na
 224            }
 225        }
 226
 14227        return activity;
 14228    }
 229
 230    private async Task<IActivity> CompileBlockAsync(BlockNode block, CancellationToken cancellationToken = default)
 231    {
 1232        var activities = new List<IActivity>();
 233
 6234        foreach (var statement in block.Statements)
 235        {
 2236            var activity = await CompileStatementAsync(statement, cancellationToken);
 2237            if (activity != null)
 238            {
 2239                activities.Add(activity);
 240            }
 241        }
 242
 1243        return new Sequence
 1244        {
 1245            Activities = activities
 1246        };
 1247    }
 248
 249    private async Task<IActivity> CompileIfAsync(IfNode ifNode, CancellationToken cancellationToken = default)
 250    {
 0251        var condition = CompileExpressionAsInput<bool>(ifNode.Condition);
 0252        var thenActivity = await CompileStatementAsync(ifNode.Then, cancellationToken);
 0253        var elseActivity = ifNode.Else != null ? await CompileStatementAsync(ifNode.Else, cancellationToken) : null;
 254
 0255        return new If(condition)
 0256        {
 0257            Then = thenActivity,
 0258            Else = elseActivity
 0259        };
 0260    }
 261
 262    private async Task<IActivity> CompileForEachAsync(ForEachNode forEach, CancellationToken cancellationToken = default
 263    {
 264        Variable loopVariable;
 265
 0266        if (forEach.DeclaresVariable)
 267        {
 268            // Create a new loop variable
 0269            loopVariable = new Variable<object>(forEach.VariableName, null!);
 0270            _variables[forEach.VariableName] = loopVariable;
 271        }
 272        else
 273        {
 274            // Reuse existing variable
 0275            if (!_variables.TryGetValue(forEach.VariableName, out loopVariable!))
 276            {
 0277                throw new InvalidOperationException($"Variable '{forEach.VariableName}' is not declared. Use 'var {forEa
 278            }
 279        }
 280
 0281        var items = CompileExpressionAsInput<ICollection<object>>(forEach.Collection);
 0282        var body = await CompileStatementAsync(forEach.Body, cancellationToken);
 283
 0284        var forEachActivity = new ForEach<object>(items)
 0285        {
 0286            CurrentValue = new(loopVariable),
 0287            Body = body
 0288        };
 289
 0290        return forEachActivity;
 0291    }
 292
 293    private async Task<IActivity> CompileForAsync(ForNode forNode, CancellationToken cancellationToken = default)
 294    {
 295        Variable loopVariable;
 296
 2297        if (forNode.DeclaresVariable)
 298        {
 299            // Create a new loop variable
 2300            loopVariable = new Variable<int>(forNode.VariableName, 0);
 2301            _variables[forNode.VariableName] = loopVariable;
 302        }
 303        else
 304        {
 305            // Reuse existing variable
 0306            if (!_variables.TryGetValue(forNode.VariableName, out loopVariable!))
 307            {
 0308                throw new InvalidOperationException($"Variable '{forNode.VariableName}' is not declared. Use 'var {forNo
 309            }
 310        }
 311
 2312        var start = CompileExpressionAsInput<int>(forNode.Start);
 2313        var end = CompileExpressionAsInput<int>(forNode.End);
 2314        var step = CompileExpressionAsInput<int>(forNode.Step);
 2315        var body = await CompileStatementAsync(forNode.Body, cancellationToken);
 316
 2317        var forActivity = new For
 2318        {
 2319            Start = start,
 2320            End = end,
 2321            Step = step,
 2322            OuterBoundInclusive = new Input<bool>(forNode.IsInclusive),
 2323            CurrentValue = new Output<object?>(loopVariable),
 2324            Body = body
 2325        };
 326
 2327        return forActivity;
 2328    }
 329
 330    private async Task<IActivity> CompileWhileAsync(WhileNode whileNode, CancellationToken cancellationToken = default)
 331    {
 0332        var condition = CompileExpressionAsInput<bool>(whileNode.Condition);
 0333        var body = await CompileStatementAsync(whileNode.Body, cancellationToken);
 334
 0335        return new While(condition)
 0336        {
 0337            Body = body
 0338        };
 0339    }
 340
 341    private async Task<IActivity> CompileSwitchAsync(SwitchNode switchNode, CancellationToken cancellationToken = defaul
 342    {
 0343        var cases = new List<SwitchCase>();
 344
 0345        foreach (var caseNode in switchNode.Cases)
 346        {
 0347            var caseExpression = CompileExpressionAsExpression(caseNode.Value);
 0348            var caseBody = await CompileStatementAsync(caseNode.Body, cancellationToken);
 0349            cases.Add(new("Case", caseExpression, caseBody!));
 0350        }
 351
 0352        var defaultActivity = switchNode.Default != null ? await CompileStatementAsync(switchNode.Default, cancellationT
 353
 0354        return new Switch
 0355        {
 0356            Cases = cases,
 0357            Default = defaultActivity
 0358        };
 0359    }
 360
 361    private async Task<IActivity> CompileFlowchartAsync(FlowchartNode flowchart, CancellationToken cancellationToken = d
 362    {
 363        // Register flowchart-scoped variables
 6364        foreach (var varDecl in flowchart.Variables)
 365        {
 0366            CompileVariableDeclaration(varDecl);
 367        }
 368
 369        // Compile all labeled activities and build a label-to-activity map
 3370        var labelToActivity = new Dictionary<string, IActivity>();
 12371        foreach (var labeledNode in flowchart.Activities)
 372        {
 3373            var activity = await CompileStatementAsync(labeledNode.Activity, cancellationToken);
 3374            if (activity != null)
 375            {
 3376                labelToActivity[labeledNode.Label] = activity;
 377            }
 3378        }
 379
 380        // Create connections
 3381        var connections = new List<Connection>();
 8382        foreach (var connNode in flowchart.Connections)
 383        {
 1384            if (!labelToActivity.TryGetValue(connNode.Source, out var sourceActivity))
 0385                throw new InvalidOperationException($"Source label '{connNode.Source}' not found in flowchart");
 386
 1387            if (!labelToActivity.TryGetValue(connNode.Target, out var targetActivity))
 0388                throw new InvalidOperationException($"Target label '{connNode.Target}' not found in flowchart");
 389
 1390            var source = new Endpoint(sourceActivity, connNode.Outcome);
 1391            var target = new Endpoint(targetActivity);
 1392            connections.Add(new Connection(source, target));
 393        }
 394
 395        // Create flowchart activity
 3396        var flowchartActivity = new Workflows.Activities.Flowchart.Activities.Flowchart
 3397        {
 3398            Activities = labelToActivity.Values.ToList(),
 3399            Connections = connections
 3400        };
 401
 402        // Set entry point if specified
 3403        if (!string.IsNullOrEmpty(flowchart.EntryPoint))
 404        {
 2405            if (!labelToActivity.TryGetValue(flowchart.EntryPoint, out var startActivity))
 0406                throw new InvalidOperationException($"Entry point label '{flowchart.EntryPoint}' not found in flowchart"
 407
 2408            flowchartActivity.Start = startActivity;
 409        }
 410
 3411        return flowchartActivity;
 3412    }
 413
 414    private async Task<IActivity> CompileListenAsync(ListenNode listen, CancellationToken cancellationToken = default)
 415    {
 416        // Listen is just a regular activity invocation that can start a workflow
 1417        var activity = await CompileActivityInvocationAsync(listen.Activity, cancellationToken);
 418
 419        // Try to set CanStartWorkflow if the activity supports it
 1420        var canStartWorkflowProp = activity.GetType().GetProperty("CanStartWorkflow");
 1421        if (canStartWorkflowProp != null && canStartWorkflowProp.PropertyType == typeof(bool))
 422        {
 1423            canStartWorkflowProp.SetValue(activity, true);
 424        }
 425
 1426        return activity;
 1427    }
 428
 429    private Input<T> CompileExpressionAsInput<T>(ExpressionNode exprNode)
 430    {
 18431        if (exprNode is LiteralNode literal)
 432        {
 12433            return new(new Literal(literal.Value!));
 434        }
 435
 6436        if (exprNode is IdentifierNode identifier)
 437        {
 438            // Reference to a variable
 1439            if (_variables.TryGetValue(identifier.Name, out var variable))
 440            {
 1441                return new(variable);
 442            }
 443
 444            // If not found, treat as a literal
 0445            return new(new Literal<T>(default!));
 446        }
 447
 5448        if (exprNode is ElsaExpressionNode elsaExpr)
 449        {
 5450            var language = elsaExpr.Language != null ? MapLanguageName(elsaExpr.Language) : _defaultExpressionLanguage;
 5451            var expression = new Expression(language, elsaExpr.Expression);
 5452            return new(expression);
 453        }
 454
 0455        if (exprNode is ArrayLiteralNode arrayLiteral)
 456        {
 457            // For array literals, evaluate to a constant array if all elements are literals
 0458            var elements = arrayLiteral.Elements.Select(EvaluateConstantExpression).ToArray();
 0459            return new((T)(object)elements);
 460        }
 461
 0462        throw new NotSupportedException($"Expression type {exprNode.GetType().Name} is not supported");
 463    }
 464
 465    private Expression CompileExpressionAsExpression(ExpressionNode exprNode)
 466    {
 0467        if (exprNode is LiteralNode literal)
 468        {
 0469            return Expression.LiteralExpression(literal.Value);
 470        }
 471
 0472        if (exprNode is ElsaExpressionNode elsaExpr)
 473        {
 0474            var language = elsaExpr.Language != null ? MapLanguageName(elsaExpr.Language) : _defaultExpressionLanguage;
 0475            return new(language, elsaExpr.Expression);
 476        }
 477
 0478        throw new NotSupportedException($"Expression type {exprNode.GetType().Name} is not supported as Expression");
 479    }
 480
 481    [UnconditionalSuppressMessage("Trimming", "IL2060:Call to MakeGenericMethod can not be statically analyzed", Justifi
 482    private object CompileExpression(ExpressionNode exprNode, Type targetType)
 483    {
 484        // Check if targetType is already Input<T>
 485        Type innerType;
 12486        if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Input<>))
 487        {
 488            // Extract the T from Input<T>
 12489            innerType = targetType.GetGenericArguments()[0];
 490        }
 491        else
 492        {
 0493            innerType = targetType;
 494        }
 495
 496        // Use reflection to call CompileExpressionAsInput<T>
 12497        var method = GetType().GetMethod(nameof(CompileExpressionAsInput),
 12498            System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
 12499        var genericMethod = method!.MakeGenericMethod(innerType);
 500
 12501        return genericMethod.Invoke(this, [exprNode])!;
 502    }
 503
 504    private object? EvaluateConstantExpression(ExpressionNode exprNode)
 505    {
 4506        if (exprNode is LiteralNode literal)
 507        {
 4508            return literal.Value;
 509        }
 510
 0511        if (exprNode is ArrayLiteralNode arrayLiteral)
 512        {
 0513            return arrayLiteral.Elements.Select(EvaluateConstantExpression).ToArray();
 514        }
 515
 516        // For non-constant expressions, return null
 0517        return null;
 518    }
 519
 520    private IActivity InstantiateActivityUsingConstructor(Type activityType, List<ArgumentNode> positionalArgs)
 521    {
 522        // Get all public constructors
 12523        var constructors = activityType.GetConstructors(System.Reflection.BindingFlags.Public | System.Reflection.Bindin
 524
 525        // Filter constructors that:
 526        // 1. Have the same number of required Input<T> parameters as positional arguments (excluding optional params)
 527        // 2. All non-optional parameters are Input<T> types
 12528        var matchingConstructors = new List<(System.Reflection.ConstructorInfo ctor, System.Reflection.ParameterInfo[] i
 529
 176530        foreach (var ctor in constructors)
 531        {
 76532            var parameters = ctor.GetParameters();
 533
 534            // Filter to only Input<T> parameters that are not optional (don't have default values or CallerMemberName a
 76535            var inputParams = parameters.Where(p =>
 228536                p.ParameterType.IsGenericType &&
 228537                p.ParameterType.GetGenericTypeDefinition() == typeof(Input<>) &&
 228538                !p.IsOptional &&
 228539                !p.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CallerFilePathAttribute), false).Any() &&
 228540                !p.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CallerLineNumberAttribute), false).Any() &
 228541                !p.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CallerMemberNameAttribute), false).Any()
 76542            ).ToArray();
 543
 544            // Check if the number of required Input<T> params matches our positional args
 76545            if (inputParams.Length == positionalArgs.Count)
 546            {
 12547                matchingConstructors.Add((ctor, inputParams));
 548            }
 549        }
 550
 12551        if (!matchingConstructors.Any())
 552        {
 0553            throw new InvalidOperationException(
 0554                $"No matching constructor found for activity type '{activityType.Name}' with {positionalArgs.Count} posi
 0555                $"Constructors must have Input<T> parameters matching the number of positional arguments.");
 556        }
 557
 12558        if (matchingConstructors.Count > 1)
 559        {
 0560            throw new InvalidOperationException(
 0561                $"Multiple matching constructors found for activity type '{activityType.Name}' with {positionalArgs.Coun
 0562                $"Please use named arguments to disambiguate.");
 563        }
 564
 12565        var (selectedCtor, selectedInputParams) = matchingConstructors[0];
 566
 567        // Build the constructor arguments
 12568        var ctorArgs = new List<object?>();
 12569        var allParams = selectedCtor.GetParameters();
 570
 96571        foreach (var param in allParams)
 572        {
 573            // Check if this is one of our Input<T> parameters
 36574            var inputParamIndex = Array.IndexOf(selectedInputParams, param);
 575
 36576            if (inputParamIndex >= 0)
 577            {
 578                // This is an Input<T> parameter - compile the corresponding positional argument
 12579                var arg = positionalArgs[inputParamIndex];
 12580                var value = CompileExpression(arg.Value, param.ParameterType);
 12581                ctorArgs.Add(value);
 582            }
 24583            else if (param.IsOptional)
 584            {
 585                // This is an optional parameter (like CallerFilePath) - use its default value
 24586                ctorArgs.Add(param.DefaultValue);
 587            }
 588            else
 589            {
 590                // This shouldn't happen if our filtering is correct
 0591                throw new InvalidOperationException(
 0592                    $"Unexpected non-optional, non-Input<T> parameter '{param.Name}' in constructor for '{activityType.N
 593            }
 594        }
 595
 596        // Instantiate the activity using the constructor
 12597        var activity = (IActivity)selectedCtor.Invoke(ctorArgs.ToArray());
 12598        return activity;
 599    }
 600
 601    private static string MapLanguageName(string dslLanguage) =>
 12602        LanguageMappings.TryGetValue(dslLanguage, out var mapped) ? mapped : dslLanguage;
 603}