diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/LanguageFlavorChange.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/LanguageFlavorChange.cs new file mode 100644 index 00000000..bab9c206 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/LanguageFlavorChange.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.Hosting.Protocol.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts +{ + + /// + /// Parameters for the Language Flavor Change notification. + /// + public class LanguageFlavorChangeParams + { + /// + /// A URI identifying the affected resource + /// + public string Uri { get; set; } + + /// + /// The primary language + /// + public string Language { get; set; } + + /// + /// The specific language flavor that is being set + /// + public string Flavor { get; set; } + } + + /// + /// Defines an event that is sent from the client to notify that + /// the client is exiting and the server should as well. + /// + public class LanguageFlavorChangeNotification + { + public static readonly + EventType Type = + EventType.Create("connection/languageflavorchanged"); + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/AutoCompleteHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/AutoCompleteHelper.cs index b702e4cd..24d7fc55 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/AutoCompleteHelper.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/AutoCompleteHelper.cs @@ -26,10 +26,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// public static class AutoCompleteHelper { - private const int PrepopulateBindTimeout = 60000; - - private static WorkspaceService workspaceServiceInstance; - private static CompletionItem[] emptyCompletionList = new CompletionItem[0]; private static readonly string[] DefaultCompletionText = new string[] @@ -354,26 +350,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices return pos > -1; } - /// - /// Gets or sets the current workspace service instance - /// Setter for internal testing purposes only - /// - internal static WorkspaceService WorkspaceServiceInstance - { - get - { - if (AutoCompleteHelper.workspaceServiceInstance == null) - { - AutoCompleteHelper.workspaceServiceInstance = WorkspaceService.Instance; - } - return AutoCompleteHelper.workspaceServiceInstance; - } - set - { - AutoCompleteHelper.workspaceServiceInstance = value; - } - } - /// /// Get the default completion list from hard-coded list /// @@ -491,93 +467,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices return completions.ToArray(); } - /// - /// Preinitialize the parser and binder with common metadata. - /// This should front load the long binding wait to the time the - /// connection is established. Once this is completed other binding - /// requests should be faster. - /// - /// - /// - internal static void PrepopulateCommonMetadata( - ConnectionInfo info, - ScriptParseInfo scriptInfo, - ConnectedBindingQueue bindingQueue) - { - if (scriptInfo.IsConnected) - { - var scriptFile = AutoCompleteHelper.WorkspaceServiceInstance.Workspace.GetFile(info.OwnerUri); - if (scriptFile == null) - { - return; - } - - LanguageService.Instance.ParseAndBind(scriptFile, info); - - if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout)) - { - try - { - QueueItem queueItem = bindingQueue.QueueBindingOperation( - key: scriptInfo.ConnectionKey, - bindingTimeout: AutoCompleteHelper.PrepopulateBindTimeout, - waitForLockTimeout: AutoCompleteHelper.PrepopulateBindTimeout, - bindOperation: (bindingContext, cancelToken) => - { - // parse a simple statement that returns common metadata - ParseResult parseResult = Parser.Parse( - "select ", - bindingContext.ParseOptions); - - List parseResults = new List(); - parseResults.Add(parseResult); - bindingContext.Binder.Bind( - parseResults, - info.ConnectionDetails.DatabaseName, - BindMode.Batch); - - // get the completion list from SQL Parser - var suggestions = Resolver.FindCompletions( - parseResult, 1, 8, - bindingContext.MetadataDisplayInfoProvider); - - // this forces lazy evaluation of the suggestion metadata - AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 8, 8); - - parseResult = Parser.Parse( - "exec ", - bindingContext.ParseOptions); - - parseResults = new List(); - parseResults.Add(parseResult); - bindingContext.Binder.Bind( - parseResults, - info.ConnectionDetails.DatabaseName, - BindMode.Batch); - - // get the completion list from SQL Parser - suggestions = Resolver.FindCompletions( - parseResult, 1, 6, - bindingContext.MetadataDisplayInfoProvider); - - // this forces lazy evaluation of the suggestion metadata - AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 6, 6); - return null; - }); - - queueItem.ItemProcessed.WaitOne(); - } - catch - { - } - finally - { - Monitor.Exit(scriptInfo.BuildingMetadataLock); - } - } - } - } - /// /// Converts a SQL Parser QuickInfo object into a VS Code Hover object /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/DiagnosticsHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/DiagnosticsHelper.cs index 41de8b55..0ae47a03 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/DiagnosticsHelper.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/DiagnosticsHelper.cs @@ -3,11 +3,14 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; +using Microsoft.SqlTools.Utility; namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { @@ -46,6 +49,29 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices }); } + /// + /// Send the diagnostic results back to the host application + /// + /// + /// + /// + internal static async Task ClearScriptDiagnostics( + string uri, + EventContext eventContext) + { + Validate.IsNotNullOrEmptyString(nameof(uri), uri); + Validate.IsNotNull(nameof(eventContext), eventContext); + // Always send syntax and semantic errors. We want to + // make sure no out-of-date markers are being displayed. + await eventContext.SendEvent( + PublishDiagnosticsNotification.Type, + new PublishDiagnosticsNotification + { + Uri = uri, + Diagnostics = Array.Empty() + }); + } + /// /// Convert a ScriptFileMarker to a Diagnostic that is Language Service compatible /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs index 44467a30..3ee1f4c5 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs @@ -4,6 +4,7 @@ // using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -36,6 +37,24 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// public sealed class LanguageService { + #region Singleton Instance Implementation + + private static readonly Lazy instance = new Lazy(() => new LanguageService()); + + /// + /// Gets the singleton instance object + /// + public static LanguageService Instance + { + get { return instance.Value; } + } + + #endregion + + #region Private / internal instance fields and constructor + private const int PrepopulateBindTimeout = 60000; + + public const string SQL_LANG = "SQL"; private const int OneSecond = 1000; internal const string DefaultBatchSeperator = "GO"; @@ -50,9 +69,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices internal const int PeekDefinitionTimeout = 10 * OneSecond; - private static ConnectionService connectionService = null; + private ConnectionService connectionService = null; - private static WorkspaceService workspaceServiceInstance; + private WorkspaceService workspaceServiceInstance; private object parseMapLock = new object(); @@ -60,51 +79,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices private ConnectedBindingQueue bindingQueue = new ConnectedBindingQueue(); - private ParseOptions defaultParseOptions = new ParseOptions( + private ParseOptions defaultParseOptions = new ParseOptions( batchSeparator: LanguageService.DefaultBatchSeperator, isQuotedIdentifierSet: true, compatibilityLevel: DatabaseCompatibilityLevel.Current, transactSqlVersion: TransactSqlVersion.Current); - /// - /// Gets or sets the binding queue instance - /// Internal for testing purposes only - /// - internal ConnectedBindingQueue BindingQueue - { - get - { - return this.bindingQueue; - } - set - { - this.bindingQueue = value; - } - } - - /// - /// Internal for testing purposes only - /// - internal static ConnectionService ConnectionServiceInstance - { - get - { - if (connectionService == null) - { - connectionService = ConnectionService.Instance; - } - return connectionService; - } - - set - { - connectionService = value; - } - } - - #region Singleton Instance Implementation - - private static readonly Lazy instance = new Lazy(() => new LanguageService()); + private ConcurrentDictionary nonMssqlUriMap = new ConcurrentDictionary(); private Lazy> scriptParseInfoMap = new Lazy>(() => new Dictionary()); @@ -120,14 +101,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } } - /// - /// Gets the singleton instance object - /// - public static LanguageService Instance - { - get { return instance.Value; } - } - private ParseOptions DefaultParseOptions { get @@ -147,42 +120,78 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices #region Properties - private static CancellationTokenSource ExistingRequestCancellation { get; set; } + /// + /// Gets or sets the binding queue instance + /// Internal for testing purposes only + /// + internal ConnectedBindingQueue BindingQueue + { + get + { + return this.bindingQueue; + } + set + { + this.bindingQueue = value; + } + } /// - /// Gets the current settings + /// Internal for testing purposes only /// - internal SqlToolsSettings CurrentSettings + internal ConnectionService ConnectionServiceInstance { - get { return WorkspaceService.Instance.CurrentSettings; } + get + { + if (connectionService == null) + { + connectionService = ConnectionService.Instance; + } + return connectionService; + } + + set + { + connectionService = value; + } } + private CancellationTokenSource existingRequestCancellation; + /// /// Gets or sets the current workspace service instance /// Setter for internal testing purposes only /// - internal static WorkspaceService WorkspaceServiceInstance + internal WorkspaceService WorkspaceServiceInstance { get { - if (LanguageService.workspaceServiceInstance == null) + if (workspaceServiceInstance == null) { - LanguageService.workspaceServiceInstance = WorkspaceService.Instance; + workspaceServiceInstance = WorkspaceService.Instance; } - return LanguageService.workspaceServiceInstance; + return workspaceServiceInstance; } set { - LanguageService.workspaceServiceInstance = value; + workspaceServiceInstance = value; } } + /// + /// Gets the current settings + /// + internal SqlToolsSettings CurrentWorkspaceSettings + { + get { return WorkspaceServiceInstance.CurrentSettings; } + } + /// /// Gets the current workspace instance /// internal Workspace.Workspace CurrentWorkspace { - get { return LanguageService.WorkspaceServiceInstance.Workspace; } + get { return WorkspaceServiceInstance.Workspace; } } /// @@ -214,6 +223,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest); serviceHost.SetRequestHandler(DefinitionRequest.Type, HandleDefinitionRequest); serviceHost.SetEventHandler(RebuildIntelliSenseNotification.Type, HandleRebuildIntelliSenseNotification); + serviceHost.SetEventHandler(LanguageFlavorChangeNotification.Type, HandleDidChangeLanguageFlavorNotification); // Register a no-op shutdown task for validation of the shutdown logic serviceHost.RegisterShutdownTask(async (shutdownParams, shutdownRequestContext) => @@ -224,13 +234,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices }); // Register the configuration update handler - WorkspaceService.Instance.RegisterConfigChangeCallback(HandleDidChangeConfigurationNotification); + WorkspaceServiceInstance.RegisterConfigChangeCallback(HandleDidChangeConfigurationNotification); // Register the file change update handler - WorkspaceService.Instance.RegisterTextDocChangeCallback(HandleDidChangeTextDocumentNotification); + WorkspaceServiceInstance.RegisterTextDocChangeCallback(HandleDidChangeTextDocumentNotification); // Register the file open update handler - WorkspaceService.Instance.RegisterTextDocOpenCallback(HandleDidOpenTextDocumentNotification); + WorkspaceServiceInstance.RegisterTextDocOpenCallback(HandleDidOpenTextDocumentNotification); // Register a callback for when a connection is created ConnectionServiceInstance.RegisterOnConnectionTask(UpdateLanguageServiceOnConnection); @@ -253,23 +263,23 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// /// /// - internal static async Task HandleCompletionRequest( + internal async Task HandleCompletionRequest( TextDocumentPosition textDocumentPosition, RequestContext requestContext) { // check if Intellisense suggestions are enabled - if (!WorkspaceService.Instance.CurrentSettings.IsSuggestionsEnabled) + if (ShouldSkipIntellisense(textDocumentPosition.TextDocument.Uri)) { await Task.FromResult(true); } else { // get the current list of completion items and return to client - var scriptFile = LanguageService.WorkspaceServiceInstance.Workspace.GetFile( + var scriptFile = CurrentWorkspace.GetFile( textDocumentPosition.TextDocument.Uri); ConnectionInfo connInfo; - LanguageService.ConnectionServiceInstance.TryFindConnection( + ConnectionServiceInstance.TryFindConnection( scriptFile.ClientFilePath, out connInfo); @@ -287,34 +297,35 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// /// /// - internal static async Task HandleCompletionResolveRequest( + internal async Task HandleCompletionResolveRequest( CompletionItem completionItem, RequestContext requestContext) { // check if Intellisense suggestions are enabled - if (!WorkspaceService.Instance.CurrentSettings.IsSuggestionsEnabled) + // Note: Do not know file, so no need to check for MSSQL flavor + if (!CurrentWorkspaceSettings.IsSuggestionsEnabled) { await Task.FromResult(true); } else { - completionItem = LanguageService.Instance.ResolveCompletionItem(completionItem); + completionItem = ResolveCompletionItem(completionItem); await requestContext.SendResult(completionItem); } } - internal static async Task HandleDefinitionRequest(TextDocumentPosition textDocumentPosition, RequestContext requestContext) + internal async Task HandleDefinitionRequest(TextDocumentPosition textDocumentPosition, RequestContext requestContext) { DocumentStatusHelper.SendStatusChange(requestContext, textDocumentPosition, DocumentStatusHelper.DefinitionRequested); - if (WorkspaceService.Instance.CurrentSettings.IsIntelliSenseEnabled) + if (!ShouldSkipIntellisense(textDocumentPosition.TextDocument.Uri)) { // Retrieve document and connection ConnectionInfo connInfo; - var scriptFile = LanguageService.WorkspaceServiceInstance.Workspace.GetFile(textDocumentPosition.TextDocument.Uri); - bool isConnected = LanguageService.ConnectionServiceInstance.TryFindConnection(scriptFile.ClientFilePath, out connInfo); + var scriptFile = CurrentWorkspace.GetFile(textDocumentPosition.TextDocument.Uri); + bool isConnected = ConnectionServiceInstance.TryFindConnection(scriptFile.ClientFilePath, out connInfo); bool succeeded = false; - DefinitionResult definitionResult = LanguageService.Instance.GetDefinition(textDocumentPosition, scriptFile, connInfo); + DefinitionResult definitionResult = GetDefinition(textDocumentPosition, scriptFile, connInfo); if (definitionResult != null) { if (definitionResult.IsErrorResult) @@ -327,6 +338,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices succeeded = true; } } + else + { + // Send an empty result so that processing does not hang + await requestContext.SendResult(Array.Empty()); + } DocumentStatusHelper.SendTelemetryEvent(requestContext, CreatePeekTelemetryProps(succeeded, isConnected)); } @@ -349,14 +365,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices // turn off this code until needed (10/28/2016) #if false - private static async Task HandleReferencesRequest( + private async Task HandleReferencesRequest( ReferencesParams referencesParams, RequestContext requestContext) { await Task.FromResult(true); } - private static async Task HandleDocumentHighlightRequest( + private async Task HandleDocumentHighlightRequest( TextDocumentPosition textDocumentPosition, RequestContext requestContext) { @@ -364,21 +380,21 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } #endif - internal static async Task HandleSignatureHelpRequest( + internal async Task HandleSignatureHelpRequest( TextDocumentPosition textDocumentPosition, RequestContext requestContext) { // check if Intellisense suggestions are enabled - if (!WorkspaceService.Instance.CurrentSettings.IsSuggestionsEnabled) + if (ShouldSkipNonMssqlFile(textDocumentPosition)) { await Task.FromResult(true); } else { - ScriptFile scriptFile = WorkspaceService.Instance.Workspace.GetFile( + ScriptFile scriptFile = CurrentWorkspace.GetFile( textDocumentPosition.TextDocument.Uri); - SignatureHelp help = LanguageService.Instance.GetSignatureHelp(textDocumentPosition, scriptFile); + SignatureHelp help = GetSignatureHelp(textDocumentPosition, scriptFile); if (help != null) { await requestContext.SendResult(help); @@ -390,17 +406,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } } - private static async Task HandleHoverRequest( + private async Task HandleHoverRequest( TextDocumentPosition textDocumentPosition, RequestContext requestContext) { // check if Quick Info hover tooltips are enabled - if (WorkspaceService.Instance.CurrentSettings.IsQuickInfoEnabled) + if (CurrentWorkspaceSettings.IsQuickInfoEnabled + && !ShouldSkipNonMssqlFile(textDocumentPosition)) { - var scriptFile = WorkspaceService.Instance.Workspace.GetFile( + var scriptFile = CurrentWorkspace.GetFile( textDocumentPosition.TextDocument.Uri); - var hover = LanguageService.Instance.GetHoverItem(textDocumentPosition, scriptFile); + var hover = GetHoverItem(textDocumentPosition, scriptFile); if (hover != null) { await requestContext.SendResult(hover); @@ -426,7 +443,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { // if not in the preview window and diagnostics are enabled then run diagnostics if (!IsPreviewWindow(scriptFile) - && WorkspaceService.Instance.CurrentSettings.IsDiagnositicsEnabled) + && CurrentWorkspaceSettings.IsDiagnosticsEnabled) { await RunScriptDiagnostics( new ScriptFile[] { scriptFile }, @@ -443,8 +460,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// public async Task HandleDidChangeTextDocumentNotification(ScriptFile[] changedFiles, EventContext eventContext) { - if (WorkspaceService.Instance.CurrentSettings.IsDiagnositicsEnabled) + if (CurrentWorkspaceSettings.IsDiagnosticsEnabled) { + // Only process files that are MSSQL flavor await this.RunScriptDiagnostics( changedFiles.ToArray(), eventContext); @@ -472,7 +490,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } ConnectionInfo connInfo; - LanguageService.ConnectionServiceInstance.TryFindConnection( + ConnectionServiceInstance.TryFindConnection( scriptFile.ClientFilePath, out connInfo); @@ -502,7 +520,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices // if not in the preview window and diagnostics are enabled then run diagnostics if (!IsPreviewWindow(scriptFile) - && WorkspaceService.Instance.CurrentSettings.IsDiagnositicsEnabled) + && CurrentWorkspaceSettings.IsDiagnosticsEnabled) { RunScriptDiagnostics( new ScriptFile[] { scriptFile }, @@ -541,20 +559,20 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices bool? oldEnableDiagnostics = oldSettings.SqlTools.IntelliSense.EnableErrorChecking; // update the current settings to reflect any changes - CurrentSettings.Update(newSettings); + CurrentWorkspaceSettings.Update(newSettings); // if script analysis settings have changed we need to clear the current diagnostic markers if (oldEnableIntelliSense != newSettings.SqlTools.IntelliSense.EnableIntellisense || oldEnableDiagnostics != newSettings.SqlTools.IntelliSense.EnableErrorChecking) { // if the user just turned off diagnostics then send an event to clear the error markers - if (!newSettings.IsDiagnositicsEnabled) + if (!newSettings.IsDiagnosticsEnabled) { ScriptFileMarker[] emptyAnalysisDiagnostics = new ScriptFileMarker[0]; - foreach (var scriptFile in WorkspaceService.Instance.Workspace.GetOpenedFiles()) + foreach (var scriptFile in CurrentWorkspace.GetOpenedFiles()) { - await DiagnosticsHelper.PublishScriptDiagnostics(scriptFile, emptyAnalysisDiagnostics, eventContext); + await DiagnosticsHelper.ClearScriptDiagnostics(scriptFile.ClientFilePath, eventContext); } } // otherwise rerun diagnostic analysis on all opened SQL files @@ -700,13 +718,158 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } } - AutoCompleteHelper.PrepopulateCommonMetadata(info, scriptInfo, this.BindingQueue); + PrepopulateCommonMetadata(info, scriptInfo, this.BindingQueue); // Send a notification to signal that autocomplete is ready ServiceHost.Instance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() {OwnerUri = info.OwnerUri}); }); } + + /// + /// Preinitialize the parser and binder with common metadata. + /// This should front load the long binding wait to the time the + /// connection is established. Once this is completed other binding + /// requests should be faster. + /// + /// + /// + internal void PrepopulateCommonMetadata( + ConnectionInfo info, + ScriptParseInfo scriptInfo, + ConnectedBindingQueue bindingQueue) + { + if (scriptInfo.IsConnected) + { + var scriptFile = CurrentWorkspace.GetFile(info.OwnerUri); + if (scriptFile == null) + { + return; + } + + ParseAndBind(scriptFile, info); + + if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout)) + { + try + { + QueueItem queueItem = bindingQueue.QueueBindingOperation( + key: scriptInfo.ConnectionKey, + bindingTimeout: PrepopulateBindTimeout, + waitForLockTimeout: PrepopulateBindTimeout, + bindOperation: (bindingContext, cancelToken) => + { + // parse a simple statement that returns common metadata + ParseResult parseResult = Parser.Parse( + "select ", + bindingContext.ParseOptions); + + List parseResults = new List(); + parseResults.Add(parseResult); + bindingContext.Binder.Bind( + parseResults, + info.ConnectionDetails.DatabaseName, + BindMode.Batch); + + // get the completion list from SQL Parser + var suggestions = Resolver.FindCompletions( + parseResult, 1, 8, + bindingContext.MetadataDisplayInfoProvider); + + // this forces lazy evaluation of the suggestion metadata + AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 8, 8); + + parseResult = Parser.Parse( + "exec ", + bindingContext.ParseOptions); + + parseResults = new List(); + parseResults.Add(parseResult); + bindingContext.Binder.Bind( + parseResults, + info.ConnectionDetails.DatabaseName, + BindMode.Batch); + + // get the completion list from SQL Parser + suggestions = Resolver.FindCompletions( + parseResult, 1, 6, + bindingContext.MetadataDisplayInfoProvider); + + // this forces lazy evaluation of the suggestion metadata + AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 6, 6); + return null; + }); + + queueItem.ItemProcessed.WaitOne(); + } + catch + { + } + finally + { + Monitor.Exit(scriptInfo.BuildingMetadataLock); + } + } + } + } + + /// + /// Handles language flavor changes by disabling intellisense on a file if it does not match the specific + /// "MSSQL" language flavor returned by our service + /// + /// + public async Task HandleDidChangeLanguageFlavorNotification( + LanguageFlavorChangeParams changeParams, + EventContext eventContext) + { + Validate.IsNotNull(nameof(changeParams), changeParams); + Validate.IsNotNull(nameof(changeParams), changeParams.Uri); + bool shouldBlock = false; + if (SQL_LANG.Equals(changeParams.Language, StringComparison.OrdinalIgnoreCase)) { + shouldBlock = !ServiceHost.ProviderName.Equals(changeParams.Flavor, StringComparison.OrdinalIgnoreCase); + } + + if (shouldBlock) { + this.nonMssqlUriMap.AddOrUpdate(changeParams.Uri, true, (k, oldValue) => true); + if (CurrentWorkspace.ContainsFile(changeParams.Uri)) + { + await DiagnosticsHelper.ClearScriptDiagnostics(changeParams.Uri, eventContext); + } + } + else + { + bool value; + this.nonMssqlUriMap.TryRemove(changeParams.Uri, out value); + } + } + + private bool ShouldSkipNonMssqlFile(TextDocumentPosition textDocPosition) + { + return ShouldSkipNonMssqlFile(textDocPosition.TextDocument.Uri); + } + + private bool ShouldSkipNonMssqlFile(ScriptFile scriptFile) + { + return ShouldSkipNonMssqlFile(scriptFile.ClientFilePath); + } + + private bool ShouldSkipNonMssqlFile(string uri) + { + bool isNonMssql = false; + nonMssqlUriMap.TryGetValue(uri, out isNonMssql); + return isNonMssql; + } + + /// + /// Determines whether intellisense should be skipped for a document. + /// If IntelliSense is disabled or it's a non-MSSQL doc this will be skipped + /// + private bool ShouldSkipIntellisense(string uri) + { + return !CurrentWorkspaceSettings.IsSuggestionsEnabled + || ShouldSkipNonMssqlFile(uri); + } + /// /// Determines whether a reparse and bind is required to provide autocomplete /// @@ -731,7 +894,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// internal CompletionItem ResolveCompletionItem(CompletionItem completionItem) { - var scriptParseInfo = LanguageService.Instance.currentCompletionParseInfo; + var scriptParseInfo = currentCompletionParseInfo; if (scriptParseInfo != null && scriptParseInfo.CurrentSuggestions != null) { if (Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock)) @@ -1055,7 +1218,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } ConnectionInfo connInfo; - LanguageService.ConnectionServiceInstance.TryFindConnection( + ConnectionServiceInstance.TryFindConnection( scriptFile.ClientFilePath, out connInfo); @@ -1131,7 +1294,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices this.currentCompletionParseInfo = null; CompletionItem[] resultCompletionItems = null; CompletionService completionService = new CompletionService(BindingQueue); - bool useLowerCaseSuggestions = this.CurrentSettings.SqlTools.IntelliSense.LowerCaseSuggestions.Value; + bool useLowerCaseSuggestions = this.CurrentWorkspaceSettings.SqlTools.IntelliSense.LowerCaseSuggestions.Value; // get the current script parse info object ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri); @@ -1179,7 +1342,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices internal ScriptFileMarker[] GetSemanticMarkers(ScriptFile scriptFile) { ConnectionInfo connInfo; - ConnectionService.Instance.TryFindConnection( + ConnectionServiceInstance.TryFindConnection( scriptFile.ClientFilePath, out connInfo); @@ -1219,7 +1382,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// private Task RunScriptDiagnostics(ScriptFile[] filesToAnalyze, EventContext eventContext) { - if (!CurrentSettings.IsDiagnositicsEnabled) + if (!CurrentWorkspaceSettings.IsDiagnosticsEnabled) { // If the user has disabled script analysis, skip it entirely return Task.FromResult(true); @@ -1228,15 +1391,15 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices // If there's an existing task, attempt to cancel it try { - if (ExistingRequestCancellation != null) + if (existingRequestCancellation != null) { // Try to cancel the request - ExistingRequestCancellation.Cancel(); + existingRequestCancellation.Cancel(); // If cancellation didn't throw an exception, // clean up the existing token - ExistingRequestCancellation.Dispose(); - ExistingRequestCancellation = null; + existingRequestCancellation.Dispose(); + existingRequestCancellation = null; } } catch (Exception e) @@ -1251,14 +1414,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices // Create a fresh cancellation token and then start the task. // We create this on a different TaskScheduler so that we // don't block the main message loop thread. - ExistingRequestCancellation = new CancellationTokenSource(); + existingRequestCancellation = new CancellationTokenSource(); Task.Factory.StartNew( () => DelayThenInvokeDiagnostics( LanguageService.DiagnosticParseDelay, filesToAnalyze, eventContext, - ExistingRequestCancellation.Token), + existingRequestCancellation.Token), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); @@ -1305,6 +1468,12 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { continue; } + else if (ShouldSkipNonMssqlFile(scriptFile.ClientFilePath)) + { + // Clear out any existing markers in case file type was changed + await DiagnosticsHelper.ClearScriptDiagnostics(scriptFile.ClientFilePath, eventContext); + continue; + } Logger.Write(LogLevel.Verbose, "Analyzing script file: " + scriptFile.FilePath); ScriptFileMarker[] semanticMarkers = GetSemanticMarkers(scriptFile); @@ -1405,4 +1574,4 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } } } -} +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/DatabaseTreeNode.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/DatabaseTreeNode.cs index ee92cfa3..777791a0 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/DatabaseTreeNode.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/SmoModel/DatabaseTreeNode.cs @@ -47,7 +47,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel } else { - ErrorMessage = string.Format(CultureInfo.InvariantCulture, SR.DatabaseNotAccessible, context.Database.Name); + if (string.IsNullOrEmpty(ErrorMessage)) + { + // Write error message if it wasn't already set during IsAccessible check + ErrorMessage = string.Format(CultureInfo.InvariantCulture, SR.DatabaseNotAccessible, context.Database.Name); + } } } @@ -63,11 +67,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel } catch (Exception ex) { - return true; string error = string.Format(CultureInfo.InvariantCulture, "Failed to get IsAccessible. error:{0} inner:{1} stacktrace:{2}", ex.Message, ex.InnerException != null ? ex.InnerException.Message : "", ex.StackTrace); Logger.Write(LogLevel.Error, error); ErrorMessage = ex.Message; + return false; } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/ServiceHost.cs b/src/Microsoft.SqlTools.ServiceLayer/ServiceHost.cs index 09356f69..b865179f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ServiceHost.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ServiceHost.cs @@ -8,15 +8,15 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; -using Microsoft.SqlTools.Extensibility; +using Microsoft.SqlTools.Extensibility; using Microsoft.SqlTools.Hosting; using Microsoft.SqlTools.Hosting.Contracts; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.Hosting.Protocol.Channel; using Microsoft.SqlTools.Utility; -using Microsoft.SqlTools.ServiceLayer.Connection; -using Microsoft.SqlTools.ServiceLayer.Admin; - +using Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlTools.ServiceLayer.Admin; + namespace Microsoft.SqlTools.ServiceLayer.Hosting { /// @@ -26,7 +26,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting /// public sealed class ServiceHost : ServiceHostBase { - private const string ProviderName = "MSSQL"; + public const string ProviderName = "MSSQL"; private const string ProviderDescription = "Microsoft SQL Server"; private const string ProviderProtocolVersion = "1.0"; @@ -62,17 +62,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting // Initialize the shutdown activities shutdownCallbacks = new List(); initializeCallbacks = new List(); - } - + } + public IMultiServiceProvider ServiceProvider { - get - { - return serviceProvider; + get + { + return serviceProvider; } - internal set - { - serviceProvider = value; + internal set + { + serviceProvider = value; } } @@ -192,8 +192,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting }); } - /// - /// Handles a request for the capabilities request + /// + /// Handles a request for the capabilities request /// internal async Task HandleCapabilitiesRequest( CapabilitiesRequest initializeParams, diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlContext/SqlToolsSettings.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlContext/SqlToolsSettings.cs index d91a3453..d2ddf9b3 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlContext/SqlToolsSettings.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlContext/SqlToolsSettings.cs @@ -58,7 +58,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext /// /// Gets a flag determining if diagnostics are enabled /// - public bool IsDiagnositicsEnabled + public bool IsDiagnosticsEnabled { get { diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/LanguageServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/LanguageServiceTests.cs index 1458c202..d6ef38e3 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/LanguageServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/LanguageServiceTests.cs @@ -52,8 +52,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer } Assert.True(LanguageService.Instance.Context != null); - Assert.True(LanguageService.ConnectionServiceInstance != null); - Assert.True(LanguageService.Instance.CurrentSettings != null); + Assert.True(LanguageService.Instance.ConnectionServiceInstance != null); + Assert.True(LanguageService.Instance.CurrentWorkspaceSettings != null); Assert.True(LanguageService.Instance.CurrentWorkspace != null); } @@ -68,7 +68,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true }; - AutoCompleteHelper.PrepopulateCommonMetadata(connInfo, scriptInfo, null); + LanguageService.Instance.PrepopulateCommonMetadata(connInfo, scriptInfo, null); } // This test currently requires a live database connection to initialize diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/AutocompleteTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/AutocompleteTests.cs index c6125823..d8bacbbd 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/AutocompleteTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/AutocompleteTests.cs @@ -19,126 +19,71 @@ using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using GlobalCommon = Microsoft.SqlTools.ServiceLayer.Test.Common; using Moq; using Xunit; +using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer { /// /// Tests for the language service autocomplete component /// - public class AutocompleteTests + public class AutocompleteTests : LanguageServiceTestBase { - private const int TaskTimeout = 60000; - - private readonly string testScriptUri = TestObjects.ScriptUri; - - private readonly string testConnectionKey = "testdbcontextkey"; - - private Mock bindingQueue; - - private Mock> workspaceService; - - private Mock> requestContext; - - private Mock scriptFile; - - private Mock binder; - - private ScriptParseInfo scriptParseInfo; - - private TextDocumentPosition textDocument; - - private void InitializeTestObjects() - { - // initial cursor position in the script file - textDocument = new TextDocumentPosition - { - TextDocument = new TextDocumentIdentifier {Uri = this.testScriptUri}, - Position = new Position - { - Line = 0, - Character = 0 - } - }; - - // default settings are stored in the workspace service - WorkspaceService.Instance.CurrentSettings = new SqlToolsSettings(); - - // set up file for returning the query - scriptFile = new Mock(); - scriptFile.SetupGet(file => file.Contents).Returns(GlobalCommon.Constants.StandardQuery); - scriptFile.SetupGet(file => file.ClientFilePath).Returns(this.testScriptUri); - - // set up workspace mock - workspaceService = new Mock>(); - workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny())) - .Returns(scriptFile.Object); - - // setup binding queue mock - bindingQueue = new Mock(); - bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny(), It.IsAny())) - .Returns(this.testConnectionKey); - - // inject mock instances into the Language Service - LanguageService.WorkspaceServiceInstance = workspaceService.Object; - LanguageService.ConnectionServiceInstance = TestObjects.GetTestConnectionService(); - ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo(); - LanguageService.ConnectionServiceInstance.OwnerToConnectionMap.Add(this.testScriptUri, connectionInfo); - LanguageService.Instance.BindingQueue = bindingQueue.Object; - - // setup the mock for SendResult - requestContext = new Mock>(); - requestContext.Setup(rc => rc.SendResult(It.IsAny())) - .Returns(Task.FromResult(0)); - - // setup the IBinder mock - binder = new Mock(); - binder.Setup(b => b.Bind( - It.IsAny>(), - It.IsAny(), - It.IsAny())); - - scriptParseInfo = new ScriptParseInfo(); - LanguageService.Instance.AddOrUpdateScriptParseInfo(this.testScriptUri, scriptParseInfo); - scriptParseInfo.IsConnected = true; - scriptParseInfo.ConnectionKey = LanguageService.Instance.BindingQueue.AddConnectionContext(connectionInfo); - - // setup the binding context object - ConnectedBindingContext bindingContext = new ConnectedBindingContext(); - bindingContext.Binder = binder.Object; - bindingContext.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider(); - LanguageService.Instance.BindingQueue.BindingContextMap.Add(scriptParseInfo.ConnectionKey, bindingContext); - } [Fact] public void HandleCompletionRequestDisabled() { InitializeTestObjects(); - WorkspaceService.Instance.CurrentSettings.SqlTools.IntelliSense.EnableIntellisense = false; - Assert.NotNull(LanguageService.HandleCompletionRequest(null, null)); + langService.CurrentWorkspaceSettings.SqlTools.IntelliSense.EnableIntellisense = false; + Assert.NotNull(langService.HandleCompletionRequest(null, null)); } [Fact] public void HandleCompletionResolveRequestDisabled() { InitializeTestObjects(); - WorkspaceService.Instance.CurrentSettings.SqlTools.IntelliSense.EnableIntellisense = false; - Assert.NotNull(LanguageService.HandleCompletionResolveRequest(null, null)); + langService.CurrentWorkspaceSettings.SqlTools.IntelliSense.EnableIntellisense = false; + Assert.NotNull(langService.HandleCompletionResolveRequest(null, null)); } [Fact] public void HandleSignatureHelpRequestDisabled() { InitializeTestObjects(); - WorkspaceService.Instance.CurrentSettings.SqlTools.IntelliSense.EnableIntellisense = false; - Assert.NotNull(LanguageService.HandleSignatureHelpRequest(null, null)); + langService.CurrentWorkspaceSettings.SqlTools.IntelliSense.EnableIntellisense = false; + Assert.NotNull(langService.HandleSignatureHelpRequest(null, null)); + } + + [Fact] + public void HandleSignatureHelpRequestNonMssqlFile() + { + InitializeTestObjects(); + + // setup the mock for SendResult + var signatureRequestContext = new Mock>(); + signatureRequestContext.Setup(rc => rc.SendResult(It.IsAny())) + .Returns(Task.FromResult(0)); + signatureRequestContext.Setup(rc => rc.SendError(It.IsAny(), It.IsAny())).Returns(Task.FromResult(0)); + + + langService.CurrentWorkspaceSettings.SqlTools.IntelliSense.EnableIntellisense = true; + langService.HandleDidChangeLanguageFlavorNotification(new LanguageFlavorChangeParams { + Uri = textDocument.TextDocument.Uri, + Language = LanguageService.SQL_LANG.ToLower(), + Flavor = "NotMSSQL" + }, null); + Assert.NotNull(langService.HandleSignatureHelpRequest(textDocument, signatureRequestContext.Object)); + + // verify that no events were sent + signatureRequestContext.Verify(m => m.SendResult(It.IsAny()), Times.Never()); + signatureRequestContext.Verify(m => m.SendError(It.IsAny(), It.IsAny()), Times.Never()); } [Fact] public void AddOrUpdateScriptParseInfoNullUri() { InitializeTestObjects(); - LanguageService.Instance.AddOrUpdateScriptParseInfo("abracadabra", scriptParseInfo); - Assert.True(LanguageService.Instance.ScriptParseInfoMap.ContainsKey("abracadabra")); + langService.AddOrUpdateScriptParseInfo("abracadabra", scriptParseInfo); + Assert.True(langService.ScriptParseInfoMap.ContainsKey("abracadabra")); } [Fact] @@ -146,21 +91,21 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer { InitializeTestObjects(); textDocument.TextDocument.Uri = "invaliduri"; - Assert.Null(LanguageService.Instance.GetDefinition(textDocument, null, null)); + Assert.Null(langService.GetDefinition(textDocument, null, null)); } [Fact] public void RemoveScriptParseInfoNullUri() { InitializeTestObjects(); - Assert.False(LanguageService.Instance.RemoveScriptParseInfo("abc123")); + Assert.False(langService.RemoveScriptParseInfo("abc123")); } [Fact] public void IsPreviewWindowNullScriptFileTest() { InitializeTestObjects(); - Assert.False(LanguageService.Instance.IsPreviewWindow(null)); + Assert.False(langService.IsPreviewWindow(null)); } [Fact] @@ -168,7 +113,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer { InitializeTestObjects(); textDocument.TextDocument.Uri = "somethinggoeshere"; - Assert.True(LanguageService.Instance.GetCompletionItems(textDocument, scriptFile.Object, null).Length > 0); + Assert.True(langService.GetCompletionItems(textDocument, scriptFile.Object, null).Length > 0); } [Fact] @@ -215,7 +160,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer InitializeTestObjects(); // request the completion list - Task handleCompletion = LanguageService.HandleCompletionRequest(textDocument, requestContext.Object); + Task handleCompletion = langService.HandleCompletionRequest(textDocument, requestContext.Object); handleCompletion.Wait(TaskTimeout); // verify that send result was called with a completion array diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/LanguageServiceTestBase.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/LanguageServiceTestBase.cs new file mode 100644 index 00000000..4642c727 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/LanguageServiceTestBase.cs @@ -0,0 +1,119 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.SqlServer.Management.SqlParser.Binder; +using Microsoft.SqlServer.Management.SqlParser.MetadataProvider; +using Microsoft.SqlServer.Management.SqlParser.Parser; +using Microsoft.SqlTools.Hosting.Protocol; +using Microsoft.SqlTools.Hosting.Protocol.Contracts; +using Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlTools.ServiceLayer.LanguageServices; +using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; +using Microsoft.SqlTools.ServiceLayer.SqlContext; +using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility; +using Microsoft.SqlTools.ServiceLayer.Workspace; +using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; +using GlobalCommon = Microsoft.SqlTools.ServiceLayer.Test.Common; +using Moq; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer +{ + /// + /// Tests for the language service autocomplete component + /// + public abstract class LanguageServiceTestBase + { + protected const int TaskTimeout = 60000; + + protected readonly string testScriptUri = TestObjects.ScriptUri; + + protected readonly string testConnectionKey = "testdbcontextkey"; + + protected LanguageService langService; + + protected Mock bindingQueue; + + protected Mock> workspaceService; + + protected Mock> requestContext; + + protected Mock scriptFile; + + protected Mock binder; + + internal ScriptParseInfo scriptParseInfo; + + protected TextDocumentPosition textDocument; + + protected void InitializeTestObjects() + { + // initial cursor position in the script file + textDocument = new TextDocumentPosition + { + TextDocument = new TextDocumentIdentifier { Uri = this.testScriptUri }, + Position = new Position + { + Line = 0, + Character = 23 + } + }; + + // default settings are stored in the workspace service + WorkspaceService.Instance.CurrentSettings = new SqlToolsSettings(); + + // set up file for returning the query + scriptFile = new Mock(); + scriptFile.SetupGet(file => file.Contents).Returns(GlobalCommon.Constants.StandardQuery); + scriptFile.SetupGet(file => file.ClientFilePath).Returns(this.testScriptUri); + + // set up workspace mock + workspaceService = new Mock>(); + workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny())) + .Returns(scriptFile.Object); + + // setup binding queue mock + bindingQueue = new Mock(); + bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny(), It.IsAny())) + .Returns(this.testConnectionKey); + + langService = new LanguageService(); + // inject mock instances into the Language Service + langService.WorkspaceServiceInstance = workspaceService.Object; + langService.ConnectionServiceInstance = TestObjects.GetTestConnectionService(); + ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo(); + langService.ConnectionServiceInstance.OwnerToConnectionMap.Add(this.testScriptUri, connectionInfo); + langService.BindingQueue = bindingQueue.Object; + + // setup the mock for SendResult + requestContext = new Mock>(); + requestContext.Setup(rc => rc.SendResult(It.IsAny())) + .Returns(Task.FromResult(0)); + requestContext.Setup(rc => rc.SendError(It.IsAny(), It.IsAny())).Returns(Task.FromResult(0)); + requestContext.Setup(r => r.SendEvent(It.IsAny>(), It.IsAny())).Returns(Task.FromResult(0)); + requestContext.Setup(r => r.SendEvent(It.IsAny>(), It.IsAny())).Returns(Task.FromResult(0)); + + // setup the IBinder mock + binder = new Mock(); + binder.Setup(b => b.Bind( + It.IsAny>(), + It.IsAny(), + It.IsAny())); + + scriptParseInfo = new ScriptParseInfo(); + langService.AddOrUpdateScriptParseInfo(this.testScriptUri, scriptParseInfo); + scriptParseInfo.IsConnected = true; + scriptParseInfo.ConnectionKey = langService.BindingQueue.AddConnectionContext(connectionInfo); + + // setup the binding context object + ConnectedBindingContext bindingContext = new ConnectedBindingContext(); + bindingContext.Binder = binder.Object; + bindingContext.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider(); + langService.BindingQueue.BindingContextMap.Add(scriptParseInfo.ConnectionKey, bindingContext); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/LanguageServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/LanguageServiceTests.cs index 57ad0fc4..8e84a9ec 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/LanguageServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/LanguageServiceTests.cs @@ -150,14 +150,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer Assert.Equal(AutoCompleteHelper.EmptyCompletionList.Length, 0); } - [Fact] - public void SetWorkspaceServiceInstanceTest() - { - AutoCompleteHelper.WorkspaceServiceInstance = null; - // workspace will be recreated if it's set to null - Assert.NotNull(AutoCompleteHelper.WorkspaceServiceInstance); - } - internal class TestScriptDocumentInfo : ScriptDocumentInfo { public TestScriptDocumentInfo(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ScriptParseInfo scriptParseInfo, diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/PeekDefinitionTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/PeekDefinitionTests.cs index b10f5877..4a18f4df 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/PeekDefinitionTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/PeekDefinitionTests.cs @@ -33,90 +33,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer /// /// Tests for the language service peek definition/ go to definition feature /// - public class PeekDefinitionTests + public class PeekDefinitionTests : LanguageServiceTestBase { - private const int TaskTimeout = 30000; - - private readonly string testScriptUri = TestObjects.ScriptUri; - - private readonly string testConnectionKey = "testdbcontextkey"; - - private Mock bindingQueue; - - private Mock> workspaceService; - - private Mock> requestContext; - - private Mock binder; - - private TextDocumentPosition textDocument; - - private void InitializeTestObjects() - { - // initial cursor position in the script file - textDocument = new TextDocumentPosition - { - TextDocument = new TextDocumentIdentifier {Uri = this.testScriptUri}, - Position = new Position - { - Line = 0, - Character = 23 - } - }; - - // default settings are stored in the workspace service - WorkspaceService.Instance.CurrentSettings = new SqlToolsSettings(); - - // set up file for returning the query - var fileMock = new Mock(); - fileMock.SetupGet(file => file.Contents).Returns(GlobalCommon.Constants.StandardQuery); - fileMock.SetupGet(file => file.ClientFilePath).Returns(this.testScriptUri); - - // set up workspace mock - workspaceService = new Mock>(); - workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny())) - .Returns(fileMock.Object); - - // setup binding queue mock - bindingQueue = new Mock(); - bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny(), It.IsAny())) - .Returns(this.testConnectionKey); - - // inject mock instances into the Language Service - LanguageService.WorkspaceServiceInstance = workspaceService.Object; - LanguageService.ConnectionServiceInstance = TestObjects.GetTestConnectionService(); - ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo(); - LanguageService.ConnectionServiceInstance.OwnerToConnectionMap.Add(this.testScriptUri, connectionInfo); - LanguageService.Instance.BindingQueue = bindingQueue.Object; - - // setup the mock for SendResult - requestContext = new Mock>(); - requestContext.Setup(rc => rc.SendResult(It.IsAny())) - .Returns(Task.FromResult(0)); - requestContext.Setup(rc => rc.SendError(It.IsAny(), It.IsAny())).Returns(Task.FromResult(0)); - requestContext.Setup(r => r.SendEvent(It.IsAny>(), It.IsAny())).Returns(Task.FromResult(0)); - requestContext.Setup(r => r.SendEvent(It.IsAny>(), It.IsAny())).Returns(Task.FromResult(0)); - - // setup the IBinder mock - binder = new Mock(); - binder.Setup(b => b.Bind( - It.IsAny>(), - It.IsAny(), - It.IsAny())); - - var testScriptParseInfo = new ScriptParseInfo(); - LanguageService.Instance.AddOrUpdateScriptParseInfo(this.testScriptUri, testScriptParseInfo); - testScriptParseInfo.IsConnected = false; - testScriptParseInfo.ConnectionKey = LanguageService.Instance.BindingQueue.AddConnectionContext(connectionInfo); - - // setup the binding context object - ConnectedBindingContext bindingContext = new ConnectedBindingContext(); - bindingContext.Binder = binder.Object; - bindingContext.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider(); - LanguageService.Instance.BindingQueue.BindingContextMap.Add(testScriptParseInfo.ConnectionKey, bindingContext); - } - - /// /// Tests the definition event handler. When called with no active connection, an error is sent /// @@ -124,8 +42,9 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer public async Task DefinitionsHandlerWithNoConnectionTest() { InitializeTestObjects(); + scriptParseInfo.IsConnected = false; // request definition - var definitionTask = await Task.WhenAny(LanguageService.HandleDefinitionRequest(textDocument, requestContext.Object), Task.Delay(TaskTimeout)); + var definitionTask = await Task.WhenAny(langService.HandleDefinitionRequest(textDocument, requestContext.Object), Task.Delay(TaskTimeout)); await definitionTask; // verify that send result was not called and send error was called requestContext.Verify(m => m.SendResult(It.IsAny()), Times.Never()); @@ -199,9 +118,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer public void DeletePeekDefinitionScriptsTest() { Scripter peekDefinition = new Scripter(null, null); - var languageService = LanguageService.Instance; Assert.True(Directory.Exists(FileUtilities.PeekDefinitionTempFolder)); - languageService.DeletePeekDefinitionScripts(); + LanguageService.Instance.DeletePeekDefinitionScripts(); Assert.False(Directory.Exists(FileUtilities.PeekDefinitionTempFolder)); } @@ -211,12 +129,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer [Fact] public void DeletePeekDefinitionScriptsWhenFolderDoesNotExistTest() { - var languageService = LanguageService.Instance; Scripter peekDefinition = new Scripter(null, null); FileUtilities.SafeDirectoryDelete(FileUtilities.PeekDefinitionTempFolder, true); Assert.False(Directory.Exists(FileUtilities.PeekDefinitionTempFolder)); // Expected not to throw any exception - languageService.DeletePeekDefinitionScripts(); + LanguageService.Instance.DeletePeekDefinitionScripts(); } /// diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/SqlContext/SettingsTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/SqlContext/SettingsTests.cs index db2093ba..f76ac8e5 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/SqlContext/SettingsTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/SqlContext/SettingsTests.cs @@ -20,7 +20,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.SqlContext public void ValidateLanguageServiceDefaults() { var sqlToolsSettings = new SqlToolsSettings(); - Assert.True(sqlToolsSettings.IsDiagnositicsEnabled); + Assert.True(sqlToolsSettings.IsDiagnosticsEnabled); Assert.True(sqlToolsSettings.IsSuggestionsEnabled); Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense); Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking); @@ -30,7 +30,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.SqlContext } /// - /// Validate that the IsDiagnositicsEnabled flag behavior + /// Validate that the IsDiagnosticsEnabled flag behavior /// [Fact] public void ValidateIsDiagnosticsEnabled() @@ -40,16 +40,16 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.SqlContext // diagnostics is enabled if IntelliSense and Diagnostics flags are set sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true; sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking = true; - Assert.True(sqlToolsSettings.IsDiagnositicsEnabled); + Assert.True(sqlToolsSettings.IsDiagnosticsEnabled); // diagnostics is disabled if either IntelliSense and Diagnostics flags is not set sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = false; sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking = true; - Assert.False(sqlToolsSettings.IsDiagnositicsEnabled); + Assert.False(sqlToolsSettings.IsDiagnosticsEnabled); sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true; sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking = false; - Assert.False(sqlToolsSettings.IsDiagnositicsEnabled); + Assert.False(sqlToolsSettings.IsDiagnosticsEnabled); } ///