From 1573e36a44c7c58dbbd20ff9ef1be6178b8d0c8b Mon Sep 17 00:00:00 2001 From: Francesco Renzi Date: Thu, 12 Mar 2026 09:50:39 +0000 Subject: [PATCH] Add expression evaluation to DapVariableProvider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add EvaluateExpression() that evaluates GitHub Actions expressions using the runner's existing PipelineTemplateEvaluator infrastructure. How it works: - Strips ${{ }} wrapper if present - Creates a BasicExpressionToken and evaluates via EvaluateStepDisplayName (supports the full expression language: functions, operators, context access) - Masks the result through MaskSecrets() — same masking path used by scope inspection - Returns a structured EvaluateResponseBody with type inference - Catches evaluation errors and returns masked error messages Also adds InferResultType() helper for DAP type hints. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Runner.Worker/Dap/DapVariableProvider.cs | 87 ++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/Runner.Worker/Dap/DapVariableProvider.cs b/src/Runner.Worker/Dap/DapVariableProvider.cs index 87f068c74..ea1fa591a 100644 --- a/src/Runner.Worker/Dap/DapVariableProvider.cs +++ b/src/Runner.Worker/Dap/DapVariableProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using GitHub.DistributedTask.ObjectTemplating.Tokens; using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.Runner.Common; @@ -170,6 +171,92 @@ namespace GitHub.Runner.Worker.Dap return _hostContext.SecretMasker.MaskSecrets(value); } + /// + /// Evaluates a GitHub Actions expression (e.g. "github.repository", + /// "${{ github.event_name }}") in the context of the current step and + /// returns a masked result suitable for the DAP evaluate response. + /// + /// Uses the runner's standard + /// so the full expression language is available (functions, operators, + /// context access). + /// + public EvaluateResponseBody EvaluateExpression(string expression, IExecutionContext context) + { + if (context?.ExpressionValues == null) + { + return new EvaluateResponseBody + { + Result = "(no execution context available)", + Type = "string", + VariablesReference = 0 + }; + } + + // Strip ${{ }} wrapper if present + var expr = expression?.Trim() ?? string.Empty; + if (expr.StartsWith("${{") && expr.EndsWith("}}")) + { + expr = expr.Substring(3, expr.Length - 5).Trim(); + } + + if (string.IsNullOrEmpty(expr)) + { + return new EvaluateResponseBody + { + Result = string.Empty, + Type = "string", + VariablesReference = 0 + }; + } + + try + { + var templateEvaluator = context.ToPipelineTemplateEvaluator(); + var token = new BasicExpressionToken(null, null, null, expr); + + var result = templateEvaluator.EvaluateStepDisplayName( + token, + context.ExpressionValues, + context.ExpressionFunctions); + + result = MaskSecrets(result ?? "null"); + + return new EvaluateResponseBody + { + Result = result, + Type = InferResultType(result), + VariablesReference = 0 + }; + } + catch (Exception ex) + { + var errorMessage = MaskSecrets($"Evaluation error: {ex.Message}"); + return new EvaluateResponseBody + { + Result = errorMessage, + Type = "string", + VariablesReference = 0 + }; + } + } + + /// + /// Infers a simple DAP type hint from the string representation of a result. + /// + internal static string InferResultType(string value) + { + if (value == null || value == "null") + return "null"; + if (value == "true" || value == "false") + return "boolean"; + if (double.TryParse(value, System.Globalization.NumberStyles.Any, + System.Globalization.CultureInfo.InvariantCulture, out _)) + return "number"; + if (value.StartsWith("{") || value.StartsWith("[")) + return "object"; + return "string"; + } + #region Private helpers private void ConvertToVariables(