diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/TSqlFormatterService.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/TSqlFormatterService.cs index f13b9e2f..61bea2dd 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Formatter/TSqlFormatterService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/TSqlFormatterService.cs @@ -53,6 +53,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Formatter get { return ServiceProvider.GetService>(); } } + /// + /// Gets the language service. Note: should handle case where this is null in cases where unit tests do not set this up + /// + private LanguageService LanguageService + { + get { return ServiceProvider.GetService(); } + } /// /// Ensure formatter settings are always up to date @@ -105,6 +112,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Formatter { return await Task.Factory.StartNew(() => { + if (ShouldSkipFormatting(docFormatParams)) + { + return Array.Empty(); + } + var range = docFormatParams.Range; ScriptFile scriptFile = GetFile(docFormatParams); if (scriptFile == null) @@ -117,10 +129,26 @@ namespace Microsoft.SqlTools.ServiceLayer.Formatter }); } - private async Task FormatAndReturnEdits(DocumentFormattingParams docFormatParams) + private bool ShouldSkipFormatting(DocumentFormattingParams docFormatParams) { + if (docFormatParams == null + || docFormatParams.TextDocument == null + || docFormatParams.TextDocument.Uri == null) + { + return true; + } + return (LanguageService != null && LanguageService.ShouldSkipNonMssqlFile(docFormatParams.TextDocument.Uri)); + } + + private async Task FormatAndReturnEdits(DocumentFormattingParams docFormatParams) + { return await Task.Factory.StartNew(() => { + if (ShouldSkipFormatting(docFormatParams)) + { + return Array.Empty(); + } + var scriptFile = GetFile(docFormatParams); if (scriptFile == null || scriptFile.FileLines.Count == 0) diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs index c5097fe7..91994a50 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs @@ -36,7 +36,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// Main class for Language Service functionality including anything that requires knowledge of /// the language to perform, such as definitions, intellisense, etc. /// - public sealed class LanguageService: IDisposable + public class LanguageService: IDisposable { #region Singleton Instance Implementation @@ -960,7 +960,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices return ShouldSkipNonMssqlFile(scriptFile.ClientFilePath); } - private bool ShouldSkipNonMssqlFile(string uri) + /// + /// Checks if a given URI is not an MSSQL file. Only files explicitly excluded by a language flavor change + /// notification will be treated as skippable + /// + public virtual bool ShouldSkipNonMssqlFile(string uri) { bool isNonMssql = false; nonMssqlUriMap.TryGetValue(uri, out isNonMssql); diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Formatter/FormatterUnitTestBase.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Formatter/FormatterUnitTestBase.cs index 79e01577..8cefa3ba 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Formatter/FormatterUnitTestBase.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Formatter/FormatterUnitTestBase.cs @@ -8,6 +8,7 @@ using System.Reflection; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.Extensibility; using Microsoft.SqlTools.ServiceLayer.Formatter; +using Microsoft.SqlTools.ServiceLayer.LanguageServices; using Microsoft.SqlTools.ServiceLayer.SqlContext; using Microsoft.SqlTools.ServiceLayer.Test.Common; using Microsoft.SqlTools.ServiceLayer.Workspace; @@ -21,8 +22,10 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter { HostMock = new Mock(); WorkspaceServiceMock = new Mock>(); + LanguageServiceMock = new Mock(); ServiceProvider = ExtensionServiceProvider.CreateDefaultServiceProvider(); ServiceProvider.RegisterSingleService(WorkspaceServiceMock.Object); + ServiceProvider.RegisterSingleService(LanguageServiceMock.Object); HostLoader.InitializeHostedServices(ServiceProvider, HostMock.Object); FormatterService = ServiceProvider.GetService(); } @@ -30,6 +33,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter protected ExtensionServiceProvider ServiceProvider { get; private set; } protected Mock HostMock { get; private set; } protected Mock> WorkspaceServiceMock { get; private set; } + protected Mock LanguageServiceMock { get; private set; } protected TSqlFormatterService FormatterService { get; private set; } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Formatter/TSqlFormatterServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Formatter/TSqlFormatterServiceTests.cs index 135d618f..49443eb1 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Formatter/TSqlFormatterServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Formatter/TSqlFormatterServiceTests.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Formatter.Contracts; +using Microsoft.SqlTools.ServiceLayer.LanguageServices; using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; using Microsoft.SqlTools.ServiceLayer.Test.Common; using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility; @@ -57,10 +58,16 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter C2 nvarchar(50) NULL )"); + private void SetupLanguageService(bool skipFile = false) + { + LanguageServiceMock.Setup(x => x.ShouldSkipNonMssqlFile(It.IsAny())).Returns(skipFile); + } + [Fact] public async Task FormatDocumentShouldReturnSingleEdit() { // Given a document that we want to format + SetupLanguageService(); SetupScriptFile(defaultSqlContents); // When format document is called await TestUtils.RunAndVerify( @@ -73,12 +80,64 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter })); } + [Fact] + public async Task FormatDocumentShouldSkipNonMssqlFile() + { + // Given a non-MSSQL document + SetupLanguageService(skipFile: true); + SetupScriptFile(defaultSqlContents); + // When format document is called + await TestUtils.RunAndVerify( + test: (requestContext) => FormatterService.HandleDocFormatRequest(docFormatParams, requestContext), + verify: (edits => + { + // Then expect a single edit to be returned and for it to match the standard formatting + Assert.Equal(0, edits.Length); + LanguageServiceMock.Verify(x => x.ShouldSkipNonMssqlFile(docFormatParams.TextDocument.Uri), Times.Once); + })); + } + + [Fact] + public async Task FormatRangeShouldReturnSingleEdit() + { + // Given a document that we want to format + SetupLanguageService(); + SetupScriptFile(defaultSqlContents); + // When format document is called + await TestUtils.RunAndVerify( + test: (requestContext) => FormatterService.HandleDocRangeFormatRequest(rangeFormatParams, requestContext), + verify: (edits => + { + // Then expect a single edit to be returned and for it to match the standard formatting + Assert.Equal(1, edits.Length); + AssertFormattingEqual(formattedSqlContents, edits[0].NewText); + })); + } + + [Fact] + public async Task FormatRangeShouldSkipNonMssqlFile() + { + // Given a non-MSSQL document + SetupLanguageService(skipFile: true); + SetupScriptFile(defaultSqlContents); + // When format document is called + await TestUtils.RunAndVerify( + test: (requestContext) => FormatterService.HandleDocRangeFormatRequest(rangeFormatParams, requestContext), + verify: (edits => + { + // Then expect a single edit to be returned and for it to match the standard formatting + Assert.Equal(0, edits.Length); + LanguageServiceMock.Verify(x => x.ShouldSkipNonMssqlFile(docFormatParams.TextDocument.Uri), Times.Once); + })); + } + + [Fact] public async Task FormatDocumentTelemetryShouldIncludeFormatTypeProperty() { await RunAndVerifyTelemetryTest( // Given a document that we want to format - preRunSetup: () => SetupScriptFile(defaultSqlContents), + preRunSetup: () => SetupLanguageService(), // When format document is called test: (requestContext) => FormatterService.HandleDocFormatRequest(docFormatParams, requestContext), verify: (result, actualParams) => @@ -95,7 +154,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter { await RunAndVerifyTelemetryTest( // Given a document that we want to format - preRunSetup: () => SetupScriptFile(defaultSqlContents), + preRunSetup: () => SetupLanguageService(), // When format range is called test: (requestContext) => FormatterService.HandleDocRangeFormatRequest(rangeFormatParams, requestContext), verify: (result, actualParams) => @@ -131,6 +190,10 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter }); // Given a document that we want to format + if (preRunSetup != null) + { + preRunSetup(); + } SetupScriptFile(defaultSqlContents); // When format document is called