From 0f0df25119a03a00c9425972e5c087101bb3360c Mon Sep 17 00:00:00 2001 From: Kim Santiago <31145923+kisantia@users.noreply.github.com> Date: Thu, 7 Oct 2021 16:13:53 -0700 Subject: [PATCH] Add error for sql bindings when .net 5 (#1259) * add error for sql bindings when .net 5 * add test * cleanup linq stuff and move out common code --- .../AzureFunctions/AddSqlBindingOperation.cs | 14 ++---- .../AzureFunctions/AzureFunctionsUtils.cs | 46 +++++++++++++++++++ .../GetAzureFunctionsOperation.cs | 10 +--- .../Localization/sr.cs | 11 +++++ .../Localization/sr.resx | 4 ++ .../Localization/sr.strings | 3 +- .../Localization/sr.xlf | 5 ++ .../AzureFunctionsNet5.cs | 26 +++++++++++ .../AzureFunctionsServiceTests.cs | 20 +++++++- ...Tools.ServiceLayer.IntegrationTests.csproj | 1 + 10 files changed, 121 insertions(+), 19 deletions(-) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AzureFunctionsUtils.cs create mode 100644 test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsNet5.cs diff --git a/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AddSqlBindingOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AddSqlBindingOperation.cs index b046bdad..b84eee8d 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AddSqlBindingOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AddSqlBindingOperation.cs @@ -23,8 +23,6 @@ namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions /// class AddSqlBindingOperation { - const string functionAttributeText = "FunctionName"; - public AddSqlBindingParams Parameters { get; } public AddSqlBindingOperation(AddSqlBindingParams parameters) @@ -43,12 +41,10 @@ namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions CompilationUnitSyntax root = tree.GetCompilationUnitRoot(); // look for Azure Function to update - IEnumerable azureFunctionMethods = from methodDeclaration in root.DescendantNodes().OfType() - where methodDeclaration.AttributeLists.Count > 0 - where methodDeclaration.AttributeLists.Where(a => a.Attributes.Where(attr => attr.Name.ToString().Contains(functionAttributeText) && attr.ArgumentList.Arguments.First().ToString().Equals($"\"{Parameters.functionName}\"")).Count() == 1).Count() == 1 - select methodDeclaration; + IEnumerable azureFunctionMethods = AzureFunctionsUtils.GetMethodsWithFunctionAttributes(root); + IEnumerable matchingMethods = azureFunctionMethods.Where(md => md.AttributeLists.Where(a => a.Attributes.Where(attr => attr.ArgumentList.Arguments.First().ToString().Equals($"\"{Parameters.functionName}\"")).Any()).Any()); - if (azureFunctionMethods.Count() == 0) + if (matchingMethods.Count() == 0) { return new ResultStatus() { @@ -56,7 +52,7 @@ namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions ErrorMessage = SR.CouldntFindAzureFunction(Parameters.functionName, Parameters.filePath) }; } - else if (azureFunctionMethods.Count() > 1) + else if (matchingMethods.Count() > 1) { return new ResultStatus() { @@ -65,7 +61,7 @@ namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions }; } - MethodDeclarationSyntax azureFunction = azureFunctionMethods.First(); + MethodDeclarationSyntax azureFunction = matchingMethods.First(); var newParam = this.Parameters.bindingType == BindingType.input ? this.GenerateInputBinding() : this.GenerateOutputBinding(); // Generate updated method with the new parameter diff --git a/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AzureFunctionsUtils.cs b/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AzureFunctionsUtils.cs new file mode 100644 index 00000000..6d4fdc02 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/AzureFunctionsUtils.cs @@ -0,0 +1,46 @@ +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions +{ + internal static class AzureFunctionsUtils + { + public const string functionAttributeText = "FunctionName"; + public const string net5FunctionAttributeText = "Function"; + + /// + /// Gets all the methods in the syntax tree with an Azure Function attribute + /// + public static IEnumerable GetMethodsWithFunctionAttributes(CompilationUnitSyntax root) + { + // Look for Azure Functions in the file + // Get all method declarations + IEnumerable methodDeclarations = root.DescendantNodes().OfType(); + + // .NET 5 is not currently supported for sql bindings, so an error should be returned if this file has .NET 5 style Azure Functions + if (HasNet5StyleAzureFunctions(methodDeclarations)) + { + throw new Exception(SR.SqlBindingsNet5NotSupported); + } + + // get all the method declarations with the FunctionName attribute + IEnumerable methodsWithFunctionAttributes = methodDeclarations.Where(md => md.AttributeLists.Where(a => a.Attributes.Where(attr => attr.Name.ToString().Equals(functionAttributeText)).Any()).Any()); + + return methodsWithFunctionAttributes; + } + + /// + /// Checks if any of the method declarations have .NET 5 style Azure Function attributes + /// .NET 5 AFs use the Function attribute, while .NET 3.1 AFs use FunctionName attritube + /// + public static bool HasNet5StyleAzureFunctions(IEnumerable methodDeclarations) + { + // get all the method declarations with the Function attribute + return methodDeclarations.Any(md => md.AttributeLists.Any(al => al.Attributes.Any(attr => attr.Name.ToString().Equals(net5FunctionAttributeText)))); + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/GetAzureFunctionsOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/GetAzureFunctionsOperation.cs index d8739f54..beaf5ab1 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/GetAzureFunctionsOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/AzureFunctions/GetAzureFunctionsOperation.cs @@ -21,8 +21,6 @@ namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions /// class GetAzureFunctionsOperation { - const string functionAttributeText = "FunctionName"; - public GetAzureFunctionsParams Parameters { get; } public GetAzureFunctionsOperation(GetAzureFunctionsParams parameters) @@ -44,15 +42,11 @@ namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions SyntaxTree tree = CSharpSyntaxTree.ParseText(text); CompilationUnitSyntax root = tree.GetCompilationUnitRoot(); - // Look for Azure Functions in the file - // Get all method declarations - IEnumerable methodDeclarations = root.DescendantNodes().OfType(); - // get all the method declarations with the FunctionName attribute - IEnumerable methodsWithFunctionAttributes = methodDeclarations.Where(md => md.AttributeLists.Count > 0).Where(md => md.AttributeLists.Where(a => a.Attributes.Where(attr => attr.Name.ToString().Contains(functionAttributeText)).Count() == 1).Count() == 1); + IEnumerable methodsWithFunctionAttributes = AzureFunctionsUtils.GetMethodsWithFunctionAttributes(root); // Get FunctionName attributes - IEnumerable functionNameAttributes = methodsWithFunctionAttributes.Select(md => md.AttributeLists.Select(a => a.Attributes.Where(attr => attr.Name.ToString().Contains(functionAttributeText)).First()).First()); + IEnumerable functionNameAttributes = methodsWithFunctionAttributes.Select(md => md.AttributeLists.Select(a => a.Attributes.Where(attr => attr.Name.ToString().Equals(AzureFunctionsUtils.functionAttributeText)).First()).First()); // Get the function names in the FunctionName attributes IEnumerable nameArgs = functionNameAttributes.Select(a => a.ArgumentList.Arguments.First()); diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs index 49808a5f..eafb79e6 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs @@ -3053,6 +3053,14 @@ namespace Microsoft.SqlTools.ServiceLayer } } + public static string SqlBindingsNet5NotSupported + { + get + { + return Keys.GetString(Keys.SqlBindingsNet5NotSupported); + } + } + public static string ConnectionServiceListDbErrorNotConnected(string uri) { return Keys.GetString(Keys.ConnectionServiceListDbErrorNotConnected, uri); @@ -4571,6 +4579,9 @@ namespace Microsoft.SqlTools.ServiceLayer public const string MoreThanOneAzureFunctionWithName = "MoreThanOneAzureFunctionWithName"; + public const string SqlBindingsNet5NotSupported = "SqlBindingsNet5NotSupported"; + + private Keys() { } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx index e575b323..c71cb5ea 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx @@ -1864,4 +1864,8 @@ . Parameters: 0 - functionName (string), 1 - fileName (string) + + Adding SQL bindings is not supported for .NET 5 + + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings index 41728c6f..d5e81b3f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings @@ -853,4 +853,5 @@ SqlAssessmentUnsuppoertedEdition(int editionCode) = Unsupported engine edition { ############################################################################ # Azure Functions CouldntFindAzureFunction(string functionName, string fileName) = Couldn't find Azure function with FunctionName '{0}' in {1} -MoreThanOneAzureFunctionWithName(string functionName, string fileName) = More than one Azure function found with the FunctionName '{0}' in {1} \ No newline at end of file +MoreThanOneAzureFunctionWithName(string functionName, string fileName) = More than one Azure function found with the FunctionName '{0}' in {1} +SqlBindingsNet5NotSupported = Adding SQL bindings is not supported for .NET 5 \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf index 736ebee4..00e755db 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf @@ -2173,6 +2173,11 @@ . Parameters: 0 - functionName (string), 1 - fileName (string) + + Adding SQL bindings is not supported for .NET 5 + Adding SQL bindings is not supported for .NET 5 + + \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsNet5.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsNet5.cs new file mode 100644 index 00000000..0ff60765 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionTestFiles/AzureFunctionsNet5.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Net; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.Extensions.Logging; + +namespace Company.Function +{ + public static class HttpTrigger1 + { + [Function("HttpTrigger1")] + public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req, + FunctionContext executionContext) + { + var logger = executionContext.GetLogger("HttpTrigger1"); + logger.LogInformation("C# HTTP trigger function processed a request."); + + var response = req.CreateResponse(HttpStatusCode.OK); + response.Headers.Add("Content-Type", "text/plain; charset=utf-8"); + + response.WriteString("Welcome to Azure Functions!"); + + return response; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionsServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionsServiceTests.cs index 471bb6b2..d81d5c9a 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionsServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/AzureFunctions/AzureFunctionsServiceTests.cs @@ -165,7 +165,6 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.AzureFunctions string testFile = Path.Join(Path.GetTempPath(), $"NoAzureFunctions-{DateTime.Now.ToString("yyyy - dd - MM--HH - mm - ss")}.cs"); FileStream fstream = File.Create(testFile); fstream.Close(); - GetAzureFunctionsParams parameters = new GetAzureFunctionsParams { filePath = testFile @@ -178,5 +177,24 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.AzureFunctions Assert.Null(result.ErrorMessage); Assert.AreEqual(0, result.azureFunctions.Length); } + + /// + /// Verify there are no errors when a file doesn't have any Azure functions + /// + [Test] + public void GetAzureFunctionsNet5() + { + string testFile = Path.Join(testAzureFunctionsFolder, "AzureFunctionsNet5.cs"); + + GetAzureFunctionsParams parameters = new GetAzureFunctionsParams + { + filePath = testFile + }; + + GetAzureFunctionsOperation operation = new GetAzureFunctionsOperation(parameters); + + Exception ex = Assert.Throws(() => { operation.GetAzureFunctions(); }); + Assert.AreEqual(SR.SqlBindingsNet5NotSupported, ex.Message); + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj index 88b67acf..c742f296 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj @@ -43,6 +43,7 @@ +