Add LanguageFlavorNotification handling and refactor LanguageService (#375)

* Add LanguageFlavorNotification handling and refactor LanguageService
- Added new notification handler for language flavor changed
- Refactored the LanguageService so that it no longer relies on so many intertwined static calls, which meant it was impossible to test without modifying the static instance. This will help with test reliability in the future, and prep for replacing the instance with a service provider.

* Skip if not an MSSQL doc and add test

* Handle definition requests

* Fix diagnostics handling
This commit is contained in:
Kevin Cunnane
2017-06-12 13:28:24 -07:00
committed by GitHub
parent b0263f8867
commit 869cd1439f
13 changed files with 537 additions and 434 deletions

View File

@@ -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
{
/// <summary>
/// Parameters for the Language Flavor Change notification.
/// </summary>
public class LanguageFlavorChangeParams
{
/// <summary>
/// A URI identifying the affected resource
/// </summary>
public string Uri { get; set; }
/// <summary>
/// The primary language
/// </summary>
public string Language { get; set; }
/// <summary>
/// The specific language flavor that is being set
/// </summary>
public string Flavor { get; set; }
}
/// <summary>
/// Defines an event that is sent from the client to notify that
/// the client is exiting and the server should as well.
/// </summary>
public class LanguageFlavorChangeNotification
{
public static readonly
EventType<LanguageFlavorChangeParams> Type =
EventType<LanguageFlavorChangeParams>.Create("connection/languageflavorchanged");
}
}

View File

@@ -26,10 +26,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// </summary> /// </summary>
public static class AutoCompleteHelper public static class AutoCompleteHelper
{ {
private const int PrepopulateBindTimeout = 60000;
private static WorkspaceService<SqlToolsSettings> workspaceServiceInstance;
private static CompletionItem[] emptyCompletionList = new CompletionItem[0]; private static CompletionItem[] emptyCompletionList = new CompletionItem[0];
private static readonly string[] DefaultCompletionText = new string[] private static readonly string[] DefaultCompletionText = new string[]
@@ -354,26 +350,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
return pos > -1; return pos > -1;
} }
/// <summary>
/// Gets or sets the current workspace service instance
/// Setter for internal testing purposes only
/// </summary>
internal static WorkspaceService<SqlToolsSettings> WorkspaceServiceInstance
{
get
{
if (AutoCompleteHelper.workspaceServiceInstance == null)
{
AutoCompleteHelper.workspaceServiceInstance = WorkspaceService<SqlToolsSettings>.Instance;
}
return AutoCompleteHelper.workspaceServiceInstance;
}
set
{
AutoCompleteHelper.workspaceServiceInstance = value;
}
}
/// <summary> /// <summary>
/// Get the default completion list from hard-coded list /// Get the default completion list from hard-coded list
/// </summary> /// </summary>
@@ -491,93 +467,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
return completions.ToArray(); return completions.ToArray();
} }
/// <summary>
/// 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.
/// </summary>
/// <param name="info"></param>
/// <param name="scriptInfo"></param>
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<ParseResult> parseResults = new List<ParseResult>();
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<ParseResult>();
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);
}
}
}
}
/// <summary> /// <summary>
/// Converts a SQL Parser QuickInfo object into a VS Code Hover object /// Converts a SQL Parser QuickInfo object into a VS Code Hover object
/// </summary> /// </summary>

View File

@@ -3,11 +3,14 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // 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.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{ {
@@ -46,6 +49,29 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}); });
} }
/// <summary>
/// Send the diagnostic results back to the host application
/// </summary>
/// <param name="scriptFile"></param>
/// <param name="semanticMarkers"></param>
/// <param name="eventContext"></param>
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<Diagnostic>()
});
}
/// <summary> /// <summary>
/// Convert a ScriptFileMarker to a Diagnostic that is Language Service compatible /// Convert a ScriptFileMarker to a Diagnostic that is Language Service compatible
/// </summary> /// </summary>

View File

@@ -4,6 +4,7 @@
// //
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@@ -36,6 +37,24 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// </summary> /// </summary>
public sealed class LanguageService public sealed class LanguageService
{ {
#region Singleton Instance Implementation
private static readonly Lazy<LanguageService> instance = new Lazy<LanguageService>(() => new LanguageService());
/// <summary>
/// Gets the singleton instance object
/// </summary>
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; private const int OneSecond = 1000;
internal const string DefaultBatchSeperator = "GO"; internal const string DefaultBatchSeperator = "GO";
@@ -50,9 +69,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
internal const int PeekDefinitionTimeout = 10 * OneSecond; internal const int PeekDefinitionTimeout = 10 * OneSecond;
private static ConnectionService connectionService = null; private ConnectionService connectionService = null;
private static WorkspaceService<SqlToolsSettings> workspaceServiceInstance; private WorkspaceService<SqlToolsSettings> workspaceServiceInstance;
private object parseMapLock = new object(); private object parseMapLock = new object();
@@ -60,51 +79,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
private ConnectedBindingQueue bindingQueue = new ConnectedBindingQueue(); private ConnectedBindingQueue bindingQueue = new ConnectedBindingQueue();
private ParseOptions defaultParseOptions = new ParseOptions( private ParseOptions defaultParseOptions = new ParseOptions(
batchSeparator: LanguageService.DefaultBatchSeperator, batchSeparator: LanguageService.DefaultBatchSeperator,
isQuotedIdentifierSet: true, isQuotedIdentifierSet: true,
compatibilityLevel: DatabaseCompatibilityLevel.Current, compatibilityLevel: DatabaseCompatibilityLevel.Current,
transactSqlVersion: TransactSqlVersion.Current); transactSqlVersion: TransactSqlVersion.Current);
/// <summary> private ConcurrentDictionary<string, bool> nonMssqlUriMap = new ConcurrentDictionary<string, bool>();
/// Gets or sets the binding queue instance
/// Internal for testing purposes only
/// </summary>
internal ConnectedBindingQueue BindingQueue
{
get
{
return this.bindingQueue;
}
set
{
this.bindingQueue = value;
}
}
/// <summary>
/// Internal for testing purposes only
/// </summary>
internal static ConnectionService ConnectionServiceInstance
{
get
{
if (connectionService == null)
{
connectionService = ConnectionService.Instance;
}
return connectionService;
}
set
{
connectionService = value;
}
}
#region Singleton Instance Implementation
private static readonly Lazy<LanguageService> instance = new Lazy<LanguageService>(() => new LanguageService());
private Lazy<Dictionary<string, ScriptParseInfo>> scriptParseInfoMap private Lazy<Dictionary<string, ScriptParseInfo>> scriptParseInfoMap
= new Lazy<Dictionary<string, ScriptParseInfo>>(() => new Dictionary<string, ScriptParseInfo>()); = new Lazy<Dictionary<string, ScriptParseInfo>>(() => new Dictionary<string, ScriptParseInfo>());
@@ -120,14 +101,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
} }
} }
/// <summary>
/// Gets the singleton instance object
/// </summary>
public static LanguageService Instance
{
get { return instance.Value; }
}
private ParseOptions DefaultParseOptions private ParseOptions DefaultParseOptions
{ {
get get
@@ -147,42 +120,78 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
#region Properties #region Properties
private static CancellationTokenSource ExistingRequestCancellation { get; set; } /// <summary>
/// Gets or sets the binding queue instance
/// Internal for testing purposes only
/// </summary>
internal ConnectedBindingQueue BindingQueue
{
get
{
return this.bindingQueue;
}
set
{
this.bindingQueue = value;
}
}
/// <summary> /// <summary>
/// Gets the current settings /// Internal for testing purposes only
/// </summary> /// </summary>
internal SqlToolsSettings CurrentSettings internal ConnectionService ConnectionServiceInstance
{ {
get { return WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings; } get
{
if (connectionService == null)
{
connectionService = ConnectionService.Instance;
}
return connectionService;
}
set
{
connectionService = value;
}
} }
private CancellationTokenSource existingRequestCancellation;
/// <summary> /// <summary>
/// Gets or sets the current workspace service instance /// Gets or sets the current workspace service instance
/// Setter for internal testing purposes only /// Setter for internal testing purposes only
/// </summary> /// </summary>
internal static WorkspaceService<SqlToolsSettings> WorkspaceServiceInstance internal WorkspaceService<SqlToolsSettings> WorkspaceServiceInstance
{ {
get get
{ {
if (LanguageService.workspaceServiceInstance == null) if (workspaceServiceInstance == null)
{ {
LanguageService.workspaceServiceInstance = WorkspaceService<SqlToolsSettings>.Instance; workspaceServiceInstance = WorkspaceService<SqlToolsSettings>.Instance;
} }
return LanguageService.workspaceServiceInstance; return workspaceServiceInstance;
} }
set set
{ {
LanguageService.workspaceServiceInstance = value; workspaceServiceInstance = value;
} }
} }
/// <summary>
/// Gets the current settings
/// </summary>
internal SqlToolsSettings CurrentWorkspaceSettings
{
get { return WorkspaceServiceInstance.CurrentSettings; }
}
/// <summary> /// <summary>
/// Gets the current workspace instance /// Gets the current workspace instance
/// </summary> /// </summary>
internal Workspace.Workspace CurrentWorkspace internal Workspace.Workspace CurrentWorkspace
{ {
get { return LanguageService.WorkspaceServiceInstance.Workspace; } get { return WorkspaceServiceInstance.Workspace; }
} }
/// <summary> /// <summary>
@@ -214,6 +223,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest); serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest);
serviceHost.SetRequestHandler(DefinitionRequest.Type, HandleDefinitionRequest); serviceHost.SetRequestHandler(DefinitionRequest.Type, HandleDefinitionRequest);
serviceHost.SetEventHandler(RebuildIntelliSenseNotification.Type, HandleRebuildIntelliSenseNotification); serviceHost.SetEventHandler(RebuildIntelliSenseNotification.Type, HandleRebuildIntelliSenseNotification);
serviceHost.SetEventHandler(LanguageFlavorChangeNotification.Type, HandleDidChangeLanguageFlavorNotification);
// Register a no-op shutdown task for validation of the shutdown logic // Register a no-op shutdown task for validation of the shutdown logic
serviceHost.RegisterShutdownTask(async (shutdownParams, shutdownRequestContext) => serviceHost.RegisterShutdownTask(async (shutdownParams, shutdownRequestContext) =>
@@ -224,13 +234,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}); });
// Register the configuration update handler // Register the configuration update handler
WorkspaceService<SqlToolsSettings>.Instance.RegisterConfigChangeCallback(HandleDidChangeConfigurationNotification); WorkspaceServiceInstance.RegisterConfigChangeCallback(HandleDidChangeConfigurationNotification);
// Register the file change update handler // Register the file change update handler
WorkspaceService<SqlToolsSettings>.Instance.RegisterTextDocChangeCallback(HandleDidChangeTextDocumentNotification); WorkspaceServiceInstance.RegisterTextDocChangeCallback(HandleDidChangeTextDocumentNotification);
// Register the file open update handler // Register the file open update handler
WorkspaceService<SqlToolsSettings>.Instance.RegisterTextDocOpenCallback(HandleDidOpenTextDocumentNotification); WorkspaceServiceInstance.RegisterTextDocOpenCallback(HandleDidOpenTextDocumentNotification);
// Register a callback for when a connection is created // Register a callback for when a connection is created
ConnectionServiceInstance.RegisterOnConnectionTask(UpdateLanguageServiceOnConnection); ConnectionServiceInstance.RegisterOnConnectionTask(UpdateLanguageServiceOnConnection);
@@ -253,23 +263,23 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <param name="textDocumentPosition"></param> /// <param name="textDocumentPosition"></param>
/// <param name="requestContext"></param> /// <param name="requestContext"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task HandleCompletionRequest( internal async Task HandleCompletionRequest(
TextDocumentPosition textDocumentPosition, TextDocumentPosition textDocumentPosition,
RequestContext<CompletionItem[]> requestContext) RequestContext<CompletionItem[]> requestContext)
{ {
// check if Intellisense suggestions are enabled // check if Intellisense suggestions are enabled
if (!WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.IsSuggestionsEnabled) if (ShouldSkipIntellisense(textDocumentPosition.TextDocument.Uri))
{ {
await Task.FromResult(true); await Task.FromResult(true);
} }
else else
{ {
// get the current list of completion items and return to client // get the current list of completion items and return to client
var scriptFile = LanguageService.WorkspaceServiceInstance.Workspace.GetFile( var scriptFile = CurrentWorkspace.GetFile(
textDocumentPosition.TextDocument.Uri); textDocumentPosition.TextDocument.Uri);
ConnectionInfo connInfo; ConnectionInfo connInfo;
LanguageService.ConnectionServiceInstance.TryFindConnection( ConnectionServiceInstance.TryFindConnection(
scriptFile.ClientFilePath, scriptFile.ClientFilePath,
out connInfo); out connInfo);
@@ -287,34 +297,35 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <param name="completionItem"></param> /// <param name="completionItem"></param>
/// <param name="requestContext"></param> /// <param name="requestContext"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task HandleCompletionResolveRequest( internal async Task HandleCompletionResolveRequest(
CompletionItem completionItem, CompletionItem completionItem,
RequestContext<CompletionItem> requestContext) RequestContext<CompletionItem> requestContext)
{ {
// check if Intellisense suggestions are enabled // check if Intellisense suggestions are enabled
if (!WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.IsSuggestionsEnabled) // Note: Do not know file, so no need to check for MSSQL flavor
if (!CurrentWorkspaceSettings.IsSuggestionsEnabled)
{ {
await Task.FromResult(true); await Task.FromResult(true);
} }
else else
{ {
completionItem = LanguageService.Instance.ResolveCompletionItem(completionItem); completionItem = ResolveCompletionItem(completionItem);
await requestContext.SendResult(completionItem); await requestContext.SendResult(completionItem);
} }
} }
internal static async Task HandleDefinitionRequest(TextDocumentPosition textDocumentPosition, RequestContext<Location[]> requestContext) internal async Task HandleDefinitionRequest(TextDocumentPosition textDocumentPosition, RequestContext<Location[]> requestContext)
{ {
DocumentStatusHelper.SendStatusChange(requestContext, textDocumentPosition, DocumentStatusHelper.DefinitionRequested); DocumentStatusHelper.SendStatusChange(requestContext, textDocumentPosition, DocumentStatusHelper.DefinitionRequested);
if (WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.IsIntelliSenseEnabled) if (!ShouldSkipIntellisense(textDocumentPosition.TextDocument.Uri))
{ {
// Retrieve document and connection // Retrieve document and connection
ConnectionInfo connInfo; ConnectionInfo connInfo;
var scriptFile = LanguageService.WorkspaceServiceInstance.Workspace.GetFile(textDocumentPosition.TextDocument.Uri); var scriptFile = CurrentWorkspace.GetFile(textDocumentPosition.TextDocument.Uri);
bool isConnected = LanguageService.ConnectionServiceInstance.TryFindConnection(scriptFile.ClientFilePath, out connInfo); bool isConnected = ConnectionServiceInstance.TryFindConnection(scriptFile.ClientFilePath, out connInfo);
bool succeeded = false; bool succeeded = false;
DefinitionResult definitionResult = LanguageService.Instance.GetDefinition(textDocumentPosition, scriptFile, connInfo); DefinitionResult definitionResult = GetDefinition(textDocumentPosition, scriptFile, connInfo);
if (definitionResult != null) if (definitionResult != null)
{ {
if (definitionResult.IsErrorResult) if (definitionResult.IsErrorResult)
@@ -327,6 +338,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
succeeded = true; succeeded = true;
} }
} }
else
{
// Send an empty result so that processing does not hang
await requestContext.SendResult(Array.Empty<Location>());
}
DocumentStatusHelper.SendTelemetryEvent(requestContext, CreatePeekTelemetryProps(succeeded, isConnected)); DocumentStatusHelper.SendTelemetryEvent(requestContext, CreatePeekTelemetryProps(succeeded, isConnected));
} }
@@ -349,14 +365,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// turn off this code until needed (10/28/2016) // turn off this code until needed (10/28/2016)
#if false #if false
private static async Task HandleReferencesRequest( private async Task HandleReferencesRequest(
ReferencesParams referencesParams, ReferencesParams referencesParams,
RequestContext<Location[]> requestContext) RequestContext<Location[]> requestContext)
{ {
await Task.FromResult(true); await Task.FromResult(true);
} }
private static async Task HandleDocumentHighlightRequest( private async Task HandleDocumentHighlightRequest(
TextDocumentPosition textDocumentPosition, TextDocumentPosition textDocumentPosition,
RequestContext<DocumentHighlight[]> requestContext) RequestContext<DocumentHighlight[]> requestContext)
{ {
@@ -364,21 +380,21 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
} }
#endif #endif
internal static async Task HandleSignatureHelpRequest( internal async Task HandleSignatureHelpRequest(
TextDocumentPosition textDocumentPosition, TextDocumentPosition textDocumentPosition,
RequestContext<SignatureHelp> requestContext) RequestContext<SignatureHelp> requestContext)
{ {
// check if Intellisense suggestions are enabled // check if Intellisense suggestions are enabled
if (!WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.IsSuggestionsEnabled) if (ShouldSkipNonMssqlFile(textDocumentPosition))
{ {
await Task.FromResult(true); await Task.FromResult(true);
} }
else else
{ {
ScriptFile scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile( ScriptFile scriptFile = CurrentWorkspace.GetFile(
textDocumentPosition.TextDocument.Uri); textDocumentPosition.TextDocument.Uri);
SignatureHelp help = LanguageService.Instance.GetSignatureHelp(textDocumentPosition, scriptFile); SignatureHelp help = GetSignatureHelp(textDocumentPosition, scriptFile);
if (help != null) if (help != null)
{ {
await requestContext.SendResult(help); await requestContext.SendResult(help);
@@ -390,17 +406,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
} }
} }
private static async Task HandleHoverRequest( private async Task HandleHoverRequest(
TextDocumentPosition textDocumentPosition, TextDocumentPosition textDocumentPosition,
RequestContext<Hover> requestContext) RequestContext<Hover> requestContext)
{ {
// check if Quick Info hover tooltips are enabled // check if Quick Info hover tooltips are enabled
if (WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.IsQuickInfoEnabled) if (CurrentWorkspaceSettings.IsQuickInfoEnabled
&& !ShouldSkipNonMssqlFile(textDocumentPosition))
{ {
var scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile( var scriptFile = CurrentWorkspace.GetFile(
textDocumentPosition.TextDocument.Uri); textDocumentPosition.TextDocument.Uri);
var hover = LanguageService.Instance.GetHoverItem(textDocumentPosition, scriptFile); var hover = GetHoverItem(textDocumentPosition, scriptFile);
if (hover != null) if (hover != null)
{ {
await requestContext.SendResult(hover); 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 not in the preview window and diagnostics are enabled then run diagnostics
if (!IsPreviewWindow(scriptFile) if (!IsPreviewWindow(scriptFile)
&& WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.IsDiagnositicsEnabled) && CurrentWorkspaceSettings.IsDiagnosticsEnabled)
{ {
await RunScriptDiagnostics( await RunScriptDiagnostics(
new ScriptFile[] { scriptFile }, new ScriptFile[] { scriptFile },
@@ -443,8 +460,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <param name="eventContext"></param> /// <param name="eventContext"></param>
public async Task HandleDidChangeTextDocumentNotification(ScriptFile[] changedFiles, EventContext eventContext) public async Task HandleDidChangeTextDocumentNotification(ScriptFile[] changedFiles, EventContext eventContext)
{ {
if (WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.IsDiagnositicsEnabled) if (CurrentWorkspaceSettings.IsDiagnosticsEnabled)
{ {
// Only process files that are MSSQL flavor
await this.RunScriptDiagnostics( await this.RunScriptDiagnostics(
changedFiles.ToArray(), changedFiles.ToArray(),
eventContext); eventContext);
@@ -472,7 +490,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
} }
ConnectionInfo connInfo; ConnectionInfo connInfo;
LanguageService.ConnectionServiceInstance.TryFindConnection( ConnectionServiceInstance.TryFindConnection(
scriptFile.ClientFilePath, scriptFile.ClientFilePath,
out connInfo); 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 not in the preview window and diagnostics are enabled then run diagnostics
if (!IsPreviewWindow(scriptFile) if (!IsPreviewWindow(scriptFile)
&& WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.IsDiagnositicsEnabled) && CurrentWorkspaceSettings.IsDiagnosticsEnabled)
{ {
RunScriptDiagnostics( RunScriptDiagnostics(
new ScriptFile[] { scriptFile }, new ScriptFile[] { scriptFile },
@@ -541,20 +559,20 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
bool? oldEnableDiagnostics = oldSettings.SqlTools.IntelliSense.EnableErrorChecking; bool? oldEnableDiagnostics = oldSettings.SqlTools.IntelliSense.EnableErrorChecking;
// update the current settings to reflect any changes // 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 script analysis settings have changed we need to clear the current diagnostic markers
if (oldEnableIntelliSense != newSettings.SqlTools.IntelliSense.EnableIntellisense if (oldEnableIntelliSense != newSettings.SqlTools.IntelliSense.EnableIntellisense
|| oldEnableDiagnostics != newSettings.SqlTools.IntelliSense.EnableErrorChecking) || oldEnableDiagnostics != newSettings.SqlTools.IntelliSense.EnableErrorChecking)
{ {
// if the user just turned off diagnostics then send an event to clear the error markers // 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]; ScriptFileMarker[] emptyAnalysisDiagnostics = new ScriptFileMarker[0];
foreach (var scriptFile in WorkspaceService<SqlToolsSettings>.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 // 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 // Send a notification to signal that autocomplete is ready
ServiceHost.Instance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() {OwnerUri = info.OwnerUri}); ServiceHost.Instance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() {OwnerUri = info.OwnerUri});
}); });
} }
/// <summary>
/// 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.
/// </summary>
/// <param name="info"></param>
/// <param name="scriptInfo"></param>
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<ParseResult> parseResults = new List<ParseResult>();
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<ParseResult>();
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);
}
}
}
}
/// <summary>
/// Handles language flavor changes by disabling intellisense on a file if it does not match the specific
/// "MSSQL" language flavor returned by our service
/// </summary>
/// <param name="info"></param>
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;
}
/// <summary>
/// Determines whether intellisense should be skipped for a document.
/// If IntelliSense is disabled or it's a non-MSSQL doc this will be skipped
/// </summary>
private bool ShouldSkipIntellisense(string uri)
{
return !CurrentWorkspaceSettings.IsSuggestionsEnabled
|| ShouldSkipNonMssqlFile(uri);
}
/// <summary> /// <summary>
/// Determines whether a reparse and bind is required to provide autocomplete /// Determines whether a reparse and bind is required to provide autocomplete
/// </summary> /// </summary>
@@ -731,7 +894,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <param name="completionItem"></param> /// <param name="completionItem"></param>
internal CompletionItem ResolveCompletionItem(CompletionItem completionItem) internal CompletionItem ResolveCompletionItem(CompletionItem completionItem)
{ {
var scriptParseInfo = LanguageService.Instance.currentCompletionParseInfo; var scriptParseInfo = currentCompletionParseInfo;
if (scriptParseInfo != null && scriptParseInfo.CurrentSuggestions != null) if (scriptParseInfo != null && scriptParseInfo.CurrentSuggestions != null)
{ {
if (Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock)) if (Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock))
@@ -1055,7 +1218,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
} }
ConnectionInfo connInfo; ConnectionInfo connInfo;
LanguageService.ConnectionServiceInstance.TryFindConnection( ConnectionServiceInstance.TryFindConnection(
scriptFile.ClientFilePath, scriptFile.ClientFilePath,
out connInfo); out connInfo);
@@ -1131,7 +1294,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
this.currentCompletionParseInfo = null; this.currentCompletionParseInfo = null;
CompletionItem[] resultCompletionItems = null; CompletionItem[] resultCompletionItems = null;
CompletionService completionService = new CompletionService(BindingQueue); 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 // get the current script parse info object
ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri); ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri);
@@ -1179,7 +1342,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
internal ScriptFileMarker[] GetSemanticMarkers(ScriptFile scriptFile) internal ScriptFileMarker[] GetSemanticMarkers(ScriptFile scriptFile)
{ {
ConnectionInfo connInfo; ConnectionInfo connInfo;
ConnectionService.Instance.TryFindConnection( ConnectionServiceInstance.TryFindConnection(
scriptFile.ClientFilePath, scriptFile.ClientFilePath,
out connInfo); out connInfo);
@@ -1219,7 +1382,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <param name="eventContext"></param> /// <param name="eventContext"></param>
private Task RunScriptDiagnostics(ScriptFile[] filesToAnalyze, EventContext eventContext) private Task RunScriptDiagnostics(ScriptFile[] filesToAnalyze, EventContext eventContext)
{ {
if (!CurrentSettings.IsDiagnositicsEnabled) if (!CurrentWorkspaceSettings.IsDiagnosticsEnabled)
{ {
// If the user has disabled script analysis, skip it entirely // If the user has disabled script analysis, skip it entirely
return Task.FromResult(true); return Task.FromResult(true);
@@ -1228,15 +1391,15 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// If there's an existing task, attempt to cancel it // If there's an existing task, attempt to cancel it
try try
{ {
if (ExistingRequestCancellation != null) if (existingRequestCancellation != null)
{ {
// Try to cancel the request // Try to cancel the request
ExistingRequestCancellation.Cancel(); existingRequestCancellation.Cancel();
// If cancellation didn't throw an exception, // If cancellation didn't throw an exception,
// clean up the existing token // clean up the existing token
ExistingRequestCancellation.Dispose(); existingRequestCancellation.Dispose();
ExistingRequestCancellation = null; existingRequestCancellation = null;
} }
} }
catch (Exception e) catch (Exception e)
@@ -1251,14 +1414,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// Create a fresh cancellation token and then start the task. // Create a fresh cancellation token and then start the task.
// We create this on a different TaskScheduler so that we // We create this on a different TaskScheduler so that we
// don't block the main message loop thread. // don't block the main message loop thread.
ExistingRequestCancellation = new CancellationTokenSource(); existingRequestCancellation = new CancellationTokenSource();
Task.Factory.StartNew( Task.Factory.StartNew(
() => () =>
DelayThenInvokeDiagnostics( DelayThenInvokeDiagnostics(
LanguageService.DiagnosticParseDelay, LanguageService.DiagnosticParseDelay,
filesToAnalyze, filesToAnalyze,
eventContext, eventContext,
ExistingRequestCancellation.Token), existingRequestCancellation.Token),
CancellationToken.None, CancellationToken.None,
TaskCreationOptions.None, TaskCreationOptions.None,
TaskScheduler.Default); TaskScheduler.Default);
@@ -1305,6 +1468,12 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{ {
continue; 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); Logger.Write(LogLevel.Verbose, "Analyzing script file: " + scriptFile.FilePath);
ScriptFileMarker[] semanticMarkers = GetSemanticMarkers(scriptFile); ScriptFileMarker[] semanticMarkers = GetSemanticMarkers(scriptFile);
@@ -1405,4 +1574,4 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
} }
} }
} }
} }

View File

@@ -47,7 +47,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
} }
else 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) catch (Exception ex)
{ {
return true;
string error = string.Format(CultureInfo.InvariantCulture, "Failed to get IsAccessible. error:{0} inner:{1} stacktrace:{2}", 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); ex.Message, ex.InnerException != null ? ex.InnerException.Message : "", ex.StackTrace);
Logger.Write(LogLevel.Error, error); Logger.Write(LogLevel.Error, error);
ErrorMessage = ex.Message; ErrorMessage = ex.Message;
return false;
} }
} }
} }

View File

@@ -8,15 +8,15 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.SqlTools.Extensibility; using Microsoft.SqlTools.Extensibility;
using Microsoft.SqlTools.Hosting; using Microsoft.SqlTools.Hosting;
using Microsoft.SqlTools.Hosting.Contracts; using Microsoft.SqlTools.Hosting.Contracts;
using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.Hosting.Protocol.Channel; using Microsoft.SqlTools.Hosting.Protocol.Channel;
using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.Utility;
using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Admin; using Microsoft.SqlTools.ServiceLayer.Admin;
namespace Microsoft.SqlTools.ServiceLayer.Hosting namespace Microsoft.SqlTools.ServiceLayer.Hosting
{ {
/// <summary> /// <summary>
@@ -26,7 +26,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
/// </summary> /// </summary>
public sealed class ServiceHost : ServiceHostBase 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 ProviderDescription = "Microsoft SQL Server";
private const string ProviderProtocolVersion = "1.0"; private const string ProviderProtocolVersion = "1.0";
@@ -62,17 +62,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
// Initialize the shutdown activities // Initialize the shutdown activities
shutdownCallbacks = new List<ShutdownCallback>(); shutdownCallbacks = new List<ShutdownCallback>();
initializeCallbacks = new List<InitializeCallback>(); initializeCallbacks = new List<InitializeCallback>();
} }
public IMultiServiceProvider ServiceProvider public IMultiServiceProvider ServiceProvider
{ {
get get
{ {
return serviceProvider; return serviceProvider;
} }
internal set internal set
{ {
serviceProvider = value; serviceProvider = value;
} }
} }
@@ -192,8 +192,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
}); });
} }
/// <summary> /// <summary>
/// Handles a request for the capabilities request /// Handles a request for the capabilities request
/// </summary> /// </summary>
internal async Task HandleCapabilitiesRequest( internal async Task HandleCapabilitiesRequest(
CapabilitiesRequest initializeParams, CapabilitiesRequest initializeParams,

View File

@@ -58,7 +58,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
/// <summary> /// <summary>
/// Gets a flag determining if diagnostics are enabled /// Gets a flag determining if diagnostics are enabled
/// </summary> /// </summary>
public bool IsDiagnositicsEnabled public bool IsDiagnosticsEnabled
{ {
get get
{ {

View File

@@ -52,8 +52,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
} }
Assert.True(LanguageService.Instance.Context != null); Assert.True(LanguageService.Instance.Context != null);
Assert.True(LanguageService.ConnectionServiceInstance != null); Assert.True(LanguageService.Instance.ConnectionServiceInstance != null);
Assert.True(LanguageService.Instance.CurrentSettings != null); Assert.True(LanguageService.Instance.CurrentWorkspaceSettings != null);
Assert.True(LanguageService.Instance.CurrentWorkspace != null); Assert.True(LanguageService.Instance.CurrentWorkspace != null);
} }
@@ -68,7 +68,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true }; 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 // This test currently requires a live database connection to initialize

View File

@@ -19,126 +19,71 @@ using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
using GlobalCommon = Microsoft.SqlTools.ServiceLayer.Test.Common; using GlobalCommon = Microsoft.SqlTools.ServiceLayer.Test.Common;
using Moq; using Moq;
using Xunit; using Xunit;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
{ {
/// <summary> /// <summary>
/// Tests for the language service autocomplete component /// Tests for the language service autocomplete component
/// </summary> /// </summary>
public class AutocompleteTests public class AutocompleteTests : LanguageServiceTestBase<CompletionItem>
{ {
private const int TaskTimeout = 60000;
private readonly string testScriptUri = TestObjects.ScriptUri;
private readonly string testConnectionKey = "testdbcontextkey";
private Mock<ConnectedBindingQueue> bindingQueue;
private Mock<WorkspaceService<SqlToolsSettings>> workspaceService;
private Mock<RequestContext<CompletionItem[]>> requestContext;
private Mock<ScriptFile> scriptFile;
private Mock<IBinder> 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<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
// set up file for returning the query
scriptFile = new Mock<ScriptFile>();
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<SqlToolsSettings>>();
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
.Returns(scriptFile.Object);
// setup binding queue mock
bindingQueue = new Mock<ConnectedBindingQueue>();
bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny<ConnectionInfo>(), It.IsAny<bool>()))
.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<CompletionItem[]>>();
requestContext.Setup(rc => rc.SendResult(It.IsAny<CompletionItem[]>()))
.Returns(Task.FromResult(0));
// setup the IBinder mock
binder = new Mock<IBinder>();
binder.Setup(b => b.Bind(
It.IsAny<IEnumerable<ParseResult>>(),
It.IsAny<string>(),
It.IsAny<BindMode>()));
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] [Fact]
public void HandleCompletionRequestDisabled() public void HandleCompletionRequestDisabled()
{ {
InitializeTestObjects(); InitializeTestObjects();
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.SqlTools.IntelliSense.EnableIntellisense = false; langService.CurrentWorkspaceSettings.SqlTools.IntelliSense.EnableIntellisense = false;
Assert.NotNull(LanguageService.HandleCompletionRequest(null, null)); Assert.NotNull(langService.HandleCompletionRequest(null, null));
} }
[Fact] [Fact]
public void HandleCompletionResolveRequestDisabled() public void HandleCompletionResolveRequestDisabled()
{ {
InitializeTestObjects(); InitializeTestObjects();
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.SqlTools.IntelliSense.EnableIntellisense = false; langService.CurrentWorkspaceSettings.SqlTools.IntelliSense.EnableIntellisense = false;
Assert.NotNull(LanguageService.HandleCompletionResolveRequest(null, null)); Assert.NotNull(langService.HandleCompletionResolveRequest(null, null));
} }
[Fact] [Fact]
public void HandleSignatureHelpRequestDisabled() public void HandleSignatureHelpRequestDisabled()
{ {
InitializeTestObjects(); InitializeTestObjects();
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.SqlTools.IntelliSense.EnableIntellisense = false; langService.CurrentWorkspaceSettings.SqlTools.IntelliSense.EnableIntellisense = false;
Assert.NotNull(LanguageService.HandleSignatureHelpRequest(null, null)); Assert.NotNull(langService.HandleSignatureHelpRequest(null, null));
}
[Fact]
public void HandleSignatureHelpRequestNonMssqlFile()
{
InitializeTestObjects();
// setup the mock for SendResult
var signatureRequestContext = new Mock<RequestContext<SignatureHelp>>();
signatureRequestContext.Setup(rc => rc.SendResult(It.IsAny<SignatureHelp>()))
.Returns(Task.FromResult(0));
signatureRequestContext.Setup(rc => rc.SendError(It.IsAny<string>(), It.IsAny<int>())).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<SignatureHelp>()), Times.Never());
signatureRequestContext.Verify(m => m.SendError(It.IsAny<string>(), It.IsAny<int>()), Times.Never());
} }
[Fact] [Fact]
public void AddOrUpdateScriptParseInfoNullUri() public void AddOrUpdateScriptParseInfoNullUri()
{ {
InitializeTestObjects(); InitializeTestObjects();
LanguageService.Instance.AddOrUpdateScriptParseInfo("abracadabra", scriptParseInfo); langService.AddOrUpdateScriptParseInfo("abracadabra", scriptParseInfo);
Assert.True(LanguageService.Instance.ScriptParseInfoMap.ContainsKey("abracadabra")); Assert.True(langService.ScriptParseInfoMap.ContainsKey("abracadabra"));
} }
[Fact] [Fact]
@@ -146,21 +91,21 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
{ {
InitializeTestObjects(); InitializeTestObjects();
textDocument.TextDocument.Uri = "invaliduri"; textDocument.TextDocument.Uri = "invaliduri";
Assert.Null(LanguageService.Instance.GetDefinition(textDocument, null, null)); Assert.Null(langService.GetDefinition(textDocument, null, null));
} }
[Fact] [Fact]
public void RemoveScriptParseInfoNullUri() public void RemoveScriptParseInfoNullUri()
{ {
InitializeTestObjects(); InitializeTestObjects();
Assert.False(LanguageService.Instance.RemoveScriptParseInfo("abc123")); Assert.False(langService.RemoveScriptParseInfo("abc123"));
} }
[Fact] [Fact]
public void IsPreviewWindowNullScriptFileTest() public void IsPreviewWindowNullScriptFileTest()
{ {
InitializeTestObjects(); InitializeTestObjects();
Assert.False(LanguageService.Instance.IsPreviewWindow(null)); Assert.False(langService.IsPreviewWindow(null));
} }
[Fact] [Fact]
@@ -168,7 +113,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
{ {
InitializeTestObjects(); InitializeTestObjects();
textDocument.TextDocument.Uri = "somethinggoeshere"; 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] [Fact]
@@ -215,7 +160,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
InitializeTestObjects(); InitializeTestObjects();
// request the completion list // request the completion list
Task handleCompletion = LanguageService.HandleCompletionRequest(textDocument, requestContext.Object); Task handleCompletion = langService.HandleCompletionRequest(textDocument, requestContext.Object);
handleCompletion.Wait(TaskTimeout); handleCompletion.Wait(TaskTimeout);
// verify that send result was called with a completion array // verify that send result was called with a completion array

View File

@@ -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
{
/// <summary>
/// Tests for the language service autocomplete component
/// </summary>
public abstract class LanguageServiceTestBase<T>
{
protected const int TaskTimeout = 60000;
protected readonly string testScriptUri = TestObjects.ScriptUri;
protected readonly string testConnectionKey = "testdbcontextkey";
protected LanguageService langService;
protected Mock<ConnectedBindingQueue> bindingQueue;
protected Mock<WorkspaceService<SqlToolsSettings>> workspaceService;
protected Mock<RequestContext<T[]>> requestContext;
protected Mock<ScriptFile> scriptFile;
protected Mock<IBinder> 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<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
// set up file for returning the query
scriptFile = new Mock<ScriptFile>();
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<SqlToolsSettings>>();
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
.Returns(scriptFile.Object);
// setup binding queue mock
bindingQueue = new Mock<ConnectedBindingQueue>();
bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny<ConnectionInfo>(), It.IsAny<bool>()))
.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<T[]>>();
requestContext.Setup(rc => rc.SendResult(It.IsAny<T[]>()))
.Returns(Task.FromResult(0));
requestContext.Setup(rc => rc.SendError(It.IsAny<string>(), It.IsAny<int>())).Returns(Task.FromResult(0));
requestContext.Setup(r => r.SendEvent(It.IsAny<EventType<TelemetryParams>>(), It.IsAny<TelemetryParams>())).Returns(Task.FromResult(0));
requestContext.Setup(r => r.SendEvent(It.IsAny<EventType<StatusChangeParams>>(), It.IsAny<StatusChangeParams>())).Returns(Task.FromResult(0));
// setup the IBinder mock
binder = new Mock<IBinder>();
binder.Setup(b => b.Bind(
It.IsAny<IEnumerable<ParseResult>>(),
It.IsAny<string>(),
It.IsAny<BindMode>()));
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);
}
}
}

View File

@@ -150,14 +150,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
Assert.Equal(AutoCompleteHelper.EmptyCompletionList.Length, 0); 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 internal class TestScriptDocumentInfo : ScriptDocumentInfo
{ {
public TestScriptDocumentInfo(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ScriptParseInfo scriptParseInfo, public TestScriptDocumentInfo(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ScriptParseInfo scriptParseInfo,

View File

@@ -33,90 +33,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
/// <summary> /// <summary>
/// Tests for the language service peek definition/ go to definition feature /// Tests for the language service peek definition/ go to definition feature
/// </summary> /// </summary>
public class PeekDefinitionTests public class PeekDefinitionTests : LanguageServiceTestBase<Location>
{ {
private const int TaskTimeout = 30000;
private readonly string testScriptUri = TestObjects.ScriptUri;
private readonly string testConnectionKey = "testdbcontextkey";
private Mock<ConnectedBindingQueue> bindingQueue;
private Mock<WorkspaceService<SqlToolsSettings>> workspaceService;
private Mock<RequestContext<Location[]>> requestContext;
private Mock<IBinder> 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<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
// set up file for returning the query
var fileMock = new Mock<ScriptFile>();
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<SqlToolsSettings>>();
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
.Returns(fileMock.Object);
// setup binding queue mock
bindingQueue = new Mock<ConnectedBindingQueue>();
bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny<ConnectionInfo>(), It.IsAny<bool>()))
.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<Location[]>>();
requestContext.Setup(rc => rc.SendResult(It.IsAny<Location[]>()))
.Returns(Task.FromResult(0));
requestContext.Setup(rc => rc.SendError(It.IsAny<string>(), It.IsAny<int>())).Returns(Task.FromResult(0));
requestContext.Setup(r => r.SendEvent(It.IsAny<EventType<TelemetryParams>>(), It.IsAny<TelemetryParams>())).Returns(Task.FromResult(0));
requestContext.Setup(r => r.SendEvent(It.IsAny<EventType<StatusChangeParams>>(), It.IsAny<StatusChangeParams>())).Returns(Task.FromResult(0));
// setup the IBinder mock
binder = new Mock<IBinder>();
binder.Setup(b => b.Bind(
It.IsAny<IEnumerable<ParseResult>>(),
It.IsAny<string>(),
It.IsAny<BindMode>()));
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);
}
/// <summary> /// <summary>
/// Tests the definition event handler. When called with no active connection, an error is sent /// Tests the definition event handler. When called with no active connection, an error is sent
/// </summary> /// </summary>
@@ -124,8 +42,9 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
public async Task DefinitionsHandlerWithNoConnectionTest() public async Task DefinitionsHandlerWithNoConnectionTest()
{ {
InitializeTestObjects(); InitializeTestObjects();
scriptParseInfo.IsConnected = false;
// request definition // 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; await definitionTask;
// verify that send result was not called and send error was called // verify that send result was not called and send error was called
requestContext.Verify(m => m.SendResult(It.IsAny<Location[]>()), Times.Never()); requestContext.Verify(m => m.SendResult(It.IsAny<Location[]>()), Times.Never());
@@ -199,9 +118,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
public void DeletePeekDefinitionScriptsTest() public void DeletePeekDefinitionScriptsTest()
{ {
Scripter peekDefinition = new Scripter(null, null); Scripter peekDefinition = new Scripter(null, null);
var languageService = LanguageService.Instance;
Assert.True(Directory.Exists(FileUtilities.PeekDefinitionTempFolder)); Assert.True(Directory.Exists(FileUtilities.PeekDefinitionTempFolder));
languageService.DeletePeekDefinitionScripts(); LanguageService.Instance.DeletePeekDefinitionScripts();
Assert.False(Directory.Exists(FileUtilities.PeekDefinitionTempFolder)); Assert.False(Directory.Exists(FileUtilities.PeekDefinitionTempFolder));
} }
@@ -211,12 +129,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
[Fact] [Fact]
public void DeletePeekDefinitionScriptsWhenFolderDoesNotExistTest() public void DeletePeekDefinitionScriptsWhenFolderDoesNotExistTest()
{ {
var languageService = LanguageService.Instance;
Scripter peekDefinition = new Scripter(null, null); Scripter peekDefinition = new Scripter(null, null);
FileUtilities.SafeDirectoryDelete(FileUtilities.PeekDefinitionTempFolder, true); FileUtilities.SafeDirectoryDelete(FileUtilities.PeekDefinitionTempFolder, true);
Assert.False(Directory.Exists(FileUtilities.PeekDefinitionTempFolder)); Assert.False(Directory.Exists(FileUtilities.PeekDefinitionTempFolder));
// Expected not to throw any exception // Expected not to throw any exception
languageService.DeletePeekDefinitionScripts(); LanguageService.Instance.DeletePeekDefinitionScripts();
} }
/// <summary> /// <summary>

View File

@@ -20,7 +20,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.SqlContext
public void ValidateLanguageServiceDefaults() public void ValidateLanguageServiceDefaults()
{ {
var sqlToolsSettings = new SqlToolsSettings(); var sqlToolsSettings = new SqlToolsSettings();
Assert.True(sqlToolsSettings.IsDiagnositicsEnabled); Assert.True(sqlToolsSettings.IsDiagnosticsEnabled);
Assert.True(sqlToolsSettings.IsSuggestionsEnabled); Assert.True(sqlToolsSettings.IsSuggestionsEnabled);
Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense); Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense);
Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking); Assert.True(sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking);
@@ -30,7 +30,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.SqlContext
} }
/// <summary> /// <summary>
/// Validate that the IsDiagnositicsEnabled flag behavior /// Validate that the IsDiagnosticsEnabled flag behavior
/// </summary> /// </summary>
[Fact] [Fact]
public void ValidateIsDiagnosticsEnabled() public void ValidateIsDiagnosticsEnabled()
@@ -40,16 +40,16 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.SqlContext
// diagnostics is enabled if IntelliSense and Diagnostics flags are set // diagnostics is enabled if IntelliSense and Diagnostics flags are set
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true; sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking = 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 // diagnostics is disabled if either IntelliSense and Diagnostics flags is not set
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = false; sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = false;
sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking = true; sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking = true;
Assert.False(sqlToolsSettings.IsDiagnositicsEnabled); Assert.False(sqlToolsSettings.IsDiagnosticsEnabled);
sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true; sqlToolsSettings.SqlTools.IntelliSense.EnableIntellisense = true;
sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking = false; sqlToolsSettings.SqlTools.IntelliSense.EnableErrorChecking = false;
Assert.False(sqlToolsSettings.IsDiagnositicsEnabled); Assert.False(sqlToolsSettings.IsDiagnosticsEnabled);
} }
/// <summary> /// <summary>