From c24014662051e2fc6740e4c291f26210e1bfcf37 Mon Sep 17 00:00:00 2001 From: Anderson Silva Date: Wed, 12 Jun 2024 18:48:10 -0500 Subject: [PATCH] Public API to get list of functions call (#2474) Issue https://github.com/microsoft/Power-Fx/issues/2455. --- .../Public/CheckResult.cs | 5 ++ .../Syntax/Visitors/ListFunctionVisitor.cs | 35 +++++++++++ .../ListFunctionVisitorTests.cs | 63 +++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 src/libraries/Microsoft.PowerFx.Core/Syntax/Visitors/ListFunctionVisitor.cs create mode 100644 src/tests/Microsoft.PowerFx.Core.Tests.Shared/ListFunctionVisitorTests.cs diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/CheckResult.cs b/src/libraries/Microsoft.PowerFx.Core/Public/CheckResult.cs index f5db126d0f..b98ec1fdc3 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Public/CheckResult.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Public/CheckResult.cs @@ -706,6 +706,11 @@ public CheckContextSummary ApplyGetContextSummary() return summary; } + + public IEnumerable GetFunctionNames() + { + return ListFunctionVisitor.Run(ApplyParse()); + } } // Internal interface to ensure that Result objects have a common contract diff --git a/src/libraries/Microsoft.PowerFx.Core/Syntax/Visitors/ListFunctionVisitor.cs b/src/libraries/Microsoft.PowerFx.Core/Syntax/Visitors/ListFunctionVisitor.cs new file mode 100644 index 0000000000..e98256a62f --- /dev/null +++ b/src/libraries/Microsoft.PowerFx.Core/Syntax/Visitors/ListFunctionVisitor.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.PowerFx.Syntax +{ + internal class ListFunctionVisitor : IdentityTexlVisitor + { + // FullName --> Name + // Use Fullname as key because it's unique. + private readonly HashSet _functionNames = new HashSet(); + + public static IEnumerable Run(ParseResult parse) + { + var visitor = new ListFunctionVisitor(); + parse.Root.Accept(visitor); + return visitor._functionNames; + } + + public override bool PreVisit(CallNode node) + { + var hasNamespace = node.Head.Namespace.Length > 0; + + var name = node.Head.Name; + var fullName = hasNamespace ? + node.Head.Namespace + "." + name : + name; + + _functionNames.Add(fullName); + + return base.PreVisit(node); + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ListFunctionVisitorTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ListFunctionVisitorTests.cs new file mode 100644 index 0000000000..2240556e16 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ListFunctionVisitorTests.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using Xunit; + +namespace Microsoft.PowerFx.Core.Tests +{ + public class ListFunctionVisitorTests : PowerFxTest + { + [Theory] + [InlineData("Abs(1)", "Abs")] + [InlineData("Abs(Abs(Abs(Abs(Abs(1)))))", "Abs")] + [InlineData("With({x:Today()}, x+1)", "With,Today")] + [InlineData("SomeNameSpace.Foo() + SomeNameSpace.Bar()", "SomeNameSpace.Foo,SomeNameSpace.Bar")] + [InlineData("true And true", "")] + [InlineData("If(true, Blank(),Error())", "If,Blank,Error")] + public void ListFunctionNamesTest(string expression, string expectedNames) + { + foreach (var textFirst in new bool[] { false, true }) + { + if (textFirst) + { + expression = $"={expression}"; + } + + CheckFunctionNames(textFirst, expression, expectedNames); + } + } + + [Theory] + [InlineData("Hello ${Sum(1,Sqrt(2))} world", "Sum,Sqrt")] + [InlineData("3 ' {} ${Upper(3+3)} \" ${Lower($\"{7+7}\")}", "Upper,Lower")] + public void ListFunctionNamesTextFirstTest(string expression, string expectedNames) + { + CheckFunctionNames(true, expression, expectedNames); + } + + [Fact] + public void ListFunctionNamesErrorTest() + { + var checkResult = new CheckResult(new Engine()); + Assert.Throws(() => checkResult.GetFunctionNames()); + } + + private static void CheckFunctionNames(bool textFirst, string expression, string expectedNames) + { + var options = new ParserOptions() { TextFirst = textFirst }; + var engine = new Engine(); + var check = engine.Check(expression, options); + var checkResult = new CheckResult(engine).SetText(expression, options); + + var functionsNames1 = check.GetFunctionNames(); + var functionsNames2 = checkResult.GetFunctionNames(); + + var actualNames1 = string.Join(",", functionsNames1); + var actualNames2 = string.Join(",", functionsNames2); + + Assert.Equal(expectedNames, actualNames1); + Assert.Equal(expectedNames, actualNames2); + } + } +}