Fix Intellisense not working for saved files (#867)

* Fix tools service to store the corrected file path

* Use ClientFilePath for key

* Further fixes

* Undo spacing changes

* Fix tests

* Trigger CI rebuild
This commit is contained in:
Charles Gagnon
2019-09-19 11:28:40 -07:00
committed by GitHub
parent 9d140b53f3
commit de1bab9f1e
13 changed files with 168 additions and 116 deletions

View File

@@ -92,7 +92,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
/// Service host object for sending/receiving requests/events. /// Service host object for sending/receiving requests/events.
/// Internal for testing purposes. /// Internal for testing purposes.
/// </summary> /// </summary>
internal IProtocolEndpoint ServiceHost { get; set;} internal IProtocolEndpoint ServiceHost { get; set; }
/// <summary> /// <summary>
/// Gets the connection queue /// Gets the connection queue
@@ -465,12 +465,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
} }
if (SqlAzureEdition.Equals(serverEdition, StringComparison.OrdinalIgnoreCase)) if (SqlAzureEdition.Equals(serverEdition, StringComparison.OrdinalIgnoreCase))
{ {
switch(serverInfo.EngineEditionId) switch (serverInfo.EngineEditionId)
{ {
case (int) DatabaseEngineEdition.SqlDataWarehouse: case (int)DatabaseEngineEdition.SqlDataWarehouse:
serverEdition = SR.AzureSqlDwEdition; serverEdition = SR.AzureSqlDwEdition;
break; break;
case (int) DatabaseEngineEdition.SqlStretchDatabase: case (int)DatabaseEngineEdition.SqlStretchDatabase:
serverEdition = SR.AzureSqlStretchEdition; serverEdition = SR.AzureSqlStretchEdition;
break; break;
default: default:
@@ -599,7 +599,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
throw new InvalidOperationException(SR.ConnectionServiceDbErrorDefaultNotConnected(ownerUri)); throw new InvalidOperationException(SR.ConnectionServiceDbErrorDefaultNotConnected(ownerUri));
} }
if(IsDedicatedAdminConnection(connectionInfo.ConnectionDetails)) if (IsDedicatedAdminConnection(connectionInfo.ConnectionDetails))
{ {
// Since this is a dedicated connection only 1 is allowed at any time. Return the default connection for use in the requested action // Since this is a dedicated connection only 1 is allowed at any time. Return the default connection for use in the requested action
connection = defaultConnection; connection = defaultConnection;
@@ -890,7 +890,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
connection.Open(); connection.Open();
List<string> results = new List<string>(); List<string> results = new List<string>();
var systemDatabases = new[] {"master", "model", "msdb", "tempdb"}; var systemDatabases = new[] { "master", "model", "msdb", "tempdb" };
using (DbCommand command = connection.CreateCommand()) using (DbCommand command = connection.CreateCommand())
{ {
command.CommandText = @"SELECT name FROM sys.databases WHERE state_desc='ONLINE' ORDER BY name ASC"; command.CommandText = @"SELECT name FROM sys.databases WHERE state_desc='ONLINE' ORDER BY name ASC";
@@ -1017,7 +1017,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
bool result = CancelConnect(cancelParams); bool result = CancelConnect(cancelParams);
await requestContext.SendResult(result); await requestContext.SendResult(result);
} }
catch(Exception ex) catch (Exception ex)
{ {
await requestContext.SendError(ex.ToString()); await requestContext.SendError(ex.ToString());
} }
@@ -1037,7 +1037,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
bool result = Instance.Disconnect(disconnectParams); bool result = Instance.Disconnect(disconnectParams);
await requestContext.SendResult(result); await requestContext.SendResult(result);
} }
catch(Exception ex) catch (Exception ex)
{ {
await requestContext.SendError(ex.ToString()); await requestContext.SendError(ex.ToString());
} }
@@ -1058,7 +1058,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
ListDatabasesResponse result = ListDatabases(listDatabasesParams); ListDatabasesResponse result = ListDatabases(listDatabasesParams);
await requestContext.SendResult(result); await requestContext.SendResult(result);
} }
catch(Exception ex) catch (Exception ex)
{ {
await requestContext.SendError(ex.ToString()); await requestContext.SendError(ex.ToString());
} }
@@ -1121,7 +1121,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
} }
if (!string.IsNullOrEmpty(connectionDetails.AuthenticationType)) if (!string.IsNullOrEmpty(connectionDetails.AuthenticationType))
{ {
switch(connectionDetails.AuthenticationType) switch (connectionDetails.AuthenticationType)
{ {
case "Integrated": case "Integrated":
connectionBuilder.IntegratedSecurity = true; connectionBuilder.IntegratedSecurity = true;

View File

@@ -41,7 +41,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
PublishDiagnosticsNotification.Type, PublishDiagnosticsNotification.Type,
new PublishDiagnosticsNotification new PublishDiagnosticsNotification
{ {
Uri = scriptFile.ClientFilePath, Uri = scriptFile.ClientUri,
Diagnostics = Diagnostics =
allMarkers allMarkers
.Select(GetDiagnosticFromMarker) .Select(GetDiagnosticFromMarker)

View File

@@ -452,7 +452,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
ConnectionInfo connInfo; ConnectionInfo connInfo;
ConnectionServiceInstance.TryFindConnection( ConnectionServiceInstance.TryFindConnection(
scriptFile.ClientFilePath, scriptFile.ClientUri,
out connInfo); out connInfo);
var completionItems = await GetCompletionItems( var completionItems = await GetCompletionItems(
@@ -514,7 +514,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
DefinitionResult definitionResult = null; DefinitionResult definitionResult = null;
if (scriptFile != null) if (scriptFile != null)
{ {
isConnected = ConnectionServiceInstance.TryFindConnection(scriptFile.ClientFilePath, out connInfo); isConnected = ConnectionServiceInstance.TryFindConnection(scriptFile.ClientUri, out connInfo);
definitionResult = GetDefinition(textDocumentPosition, scriptFile, connInfo); definitionResult = GetDefinition(textDocumentPosition, scriptFile, connInfo);
} }
@@ -723,7 +723,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
ConnectionInfo connInfo; ConnectionInfo connInfo;
ConnectionServiceInstance.TryFindConnection( ConnectionServiceInstance.TryFindConnection(
scriptFile.ClientFilePath, scriptFile.ClientUri,
out connInfo); out connInfo);
// check that there is an active connection for the current editor // check that there is an active connection for the current editor
@@ -809,7 +809,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
foreach (var scriptFile in CurrentWorkspace.GetOpenedFiles()) foreach (var scriptFile in CurrentWorkspace.GetOpenedFiles())
{ {
await DiagnosticsHelper.ClearScriptDiagnostics(scriptFile.ClientFilePath, eventContext); await DiagnosticsHelper.ClearScriptDiagnostics(scriptFile.ClientUri, eventContext);
} }
} }
// otherwise rerun diagnostic analysis on all opened SQL files // otherwise rerun diagnostic analysis on all opened SQL files
@@ -898,7 +898,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
public ParseResult ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo) public ParseResult ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo)
{ {
// get or create the current parse info object // get or create the current parse info object
ScriptParseInfo parseInfo = GetScriptParseInfo(scriptFile.ClientFilePath, createIfNotExists: true); ScriptParseInfo parseInfo = GetScriptParseInfo(scriptFile.ClientUri, createIfNotExists: true);
if (Monitor.TryEnter(parseInfo.BuildingMetadataLock, LanguageService.BindingTimeout)) if (Monitor.TryEnter(parseInfo.BuildingMetadataLock, LanguageService.BindingTimeout))
{ {
@@ -1130,7 +1130,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
private bool ShouldSkipNonMssqlFile(ScriptFile scriptFile) private bool ShouldSkipNonMssqlFile(ScriptFile scriptFile)
{ {
return ShouldSkipNonMssqlFile(scriptFile.ClientFilePath); return ShouldSkipNonMssqlFile(scriptFile.ClientUri);
} }
/// <summary> /// <summary>
@@ -1335,7 +1335,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
internal DefinitionResult GetDefinition(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ConnectionInfo connInfo) internal DefinitionResult GetDefinition(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ConnectionInfo connInfo)
{ {
// Parse sql // Parse sql
ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri); ScriptParseInfo scriptParseInfo = GetScriptParseInfo(scriptFile.ClientUri);
if (scriptParseInfo == null) if (scriptParseInfo == null)
{ {
return null; return null;
@@ -1450,7 +1450,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
textDocumentPosition.Position.Character); textDocumentPosition.Position.Character);
int endColumn = textDocumentPosition.Position.Character; int endColumn = textDocumentPosition.Position.Character;
ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri); ScriptParseInfo scriptParseInfo = GetScriptParseInfo(scriptFile.ClientUri);
if (scriptParseInfo != null && scriptParseInfo.ParseResult != null) if (scriptParseInfo != null && scriptParseInfo.ParseResult != null)
{ {
if (Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock)) if (Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock))
@@ -1499,7 +1499,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
int startLine = textDocumentPosition.Position.Line; int startLine = textDocumentPosition.Position.Line;
int endColumn = textDocumentPosition.Position.Character; int endColumn = textDocumentPosition.Position.Character;
ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri); ScriptParseInfo scriptParseInfo = GetScriptParseInfo(scriptFile.ClientUri);
if (scriptParseInfo == null) if (scriptParseInfo == null)
{ {
@@ -1509,7 +1509,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
ConnectionInfo connInfo; ConnectionInfo connInfo;
ConnectionServiceInstance.TryFindConnection( ConnectionServiceInstance.TryFindConnection(
scriptFile.ClientFilePath, scriptFile.ClientUri,
out connInfo); out connInfo);
// reparse and bind the SQL statement if needed // reparse and bind the SQL statement if needed
@@ -1587,7 +1587,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
bool useLowerCaseSuggestions = this.CurrentWorkspaceSettings.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(scriptFile.ClientUri);
if (scriptParseInfo == null) if (scriptParseInfo == null)
{ {
@@ -1672,7 +1672,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{ {
ConnectionInfo connInfo; ConnectionInfo connInfo;
ConnectionServiceInstance.TryFindConnection( ConnectionServiceInstance.TryFindConnection(
scriptFile.ClientFilePath, scriptFile.ClientUri,
out connInfo); out connInfo);
var parseResult = ParseAndBind(scriptFile, connInfo); var parseResult = ParseAndBind(scriptFile, connInfo);
@@ -1797,10 +1797,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{ {
continue; continue;
} }
else if (ShouldSkipNonMssqlFile(scriptFile.ClientFilePath)) else if (ShouldSkipNonMssqlFile(scriptFile.ClientUri))
{ {
// Clear out any existing markers in case file type was changed // Clear out any existing markers in case file type was changed
await DiagnosticsHelper.ClearScriptDiagnostics(scriptFile.ClientFilePath, eventContext); await DiagnosticsHelper.ClearScriptDiagnostics(scriptFile.ClientUri, eventContext);
continue; continue;
} }
@@ -1884,9 +1884,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <param name="scriptFile"></param> /// <param name="scriptFile"></param>
internal bool IsPreviewWindow(ScriptFile scriptFile) internal bool IsPreviewWindow(ScriptFile scriptFile)
{ {
if (scriptFile != null && !string.IsNullOrWhiteSpace(scriptFile.ClientFilePath)) if (scriptFile != null && !string.IsNullOrWhiteSpace(scriptFile.ClientUri))
{ {
return scriptFile.ClientFilePath.StartsWith("tsqloutput:"); return scriptFile.ClientUri.StartsWith("tsqloutput:");
} }
else else
{ {

View File

@@ -138,16 +138,23 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
} }
} }
internal static ResolvedFile TryGetFullPath(string filePath) /// <summary>
/// Attempts to resolve the given filePath to an absolute path to a file on disk,
/// defaulting to the original filePath if that fails.
/// </summary>
/// <param name="filePath">The file path to resolve</param>
/// <param name="clientUri">The full file path URI used by the client</param>
/// <returns></returns>
internal static ResolvedFile TryGetFullPath(string filePath, string clientUri)
{ {
try try
{ {
return new ResolvedFile(Path.GetFullPath(filePath), true); return new ResolvedFile(Path.GetFullPath(filePath), clientUri, true);
} }
catch(NotSupportedException) catch(NotSupportedException)
{ {
// This is not a standard path. // This is not a standard path.
return new ResolvedFile(filePath, false); return new ResolvedFile(filePath, clientUri, false);
} }
} }
} }

View File

@@ -19,20 +19,24 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
/// </summary> /// </summary>
internal class ResolvedFile internal class ResolvedFile
{ {
public ResolvedFile(string filePath, bool canReadFromDisk) public ResolvedFile(string filePath, string clientUri, bool canReadFromDisk)
{ {
FilePath = filePath; FilePath = filePath;
ClientUri = clientUri;
CanReadFromDisk = canReadFromDisk; CanReadFromDisk = canReadFromDisk;
} }
public string FilePath { get; private set; } public string FilePath { get; private set; }
public string ClientUri { get; private set; }
public bool CanReadFromDisk { get; private set; } public bool CanReadFromDisk { get; private set; }
public string LowercaseFilePath public string LowercaseClientUri
{ {
get get
{ {
return FilePath?.ToLower(); return ClientUri?.ToLower();
} }
} }
} }

View File

@@ -21,11 +21,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
/// <summary> /// <summary>
/// Gets a unique string that identifies this file. At this time, /// Gets a unique string that identifies this file. At this time,
/// this property returns a normalized version of the value stored /// this property returns a normalized version of the value stored
/// in the FilePath property. /// in the ClientUri property.
/// </summary> /// </summary>
public string Id public string Id
{ {
get { return this.FilePath.ToLower(); } get { return this.ClientUri.ToLower(); }
} }
/// <summary> /// <summary>
@@ -34,11 +34,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
public string FilePath { get; private set; } public string FilePath { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the path which the editor client uses to identify this file. /// Gets or sets the URI which the editor client uses to identify this file.
/// Setter for testing purposes only /// Setter for testing purposes only
/// virtual to allow mocking. /// virtual to allow mocking.
/// </summary> /// </summary>
public virtual string ClientFilePath { get; internal set; } public virtual string ClientUri { get; internal set; }
/// <summary> /// <summary>
/// Gets or sets a boolean that determines whether /// Gets or sets a boolean that determines whether
@@ -103,7 +103,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
/// </summary> /// </summary>
public ScriptFile() public ScriptFile()
{ {
ClientFilePath = "test.sql"; ClientUri = "test.sql";
} }
/// <summary> /// <summary>
@@ -111,15 +111,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
/// the given TextReader. /// the given TextReader.
/// </summary> /// </summary>
/// <param name="filePath">The path at which the script file resides.</param> /// <param name="filePath">The path at which the script file resides.</param>
/// <param name="clientFilePath">The path which the client uses to identify the file.</param> /// <param name="clientUri">The URI which the client uses to identify the file.</param>
/// <param name="textReader">The TextReader to use for reading the file's contents.</param> /// <param name="textReader">The TextReader to use for reading the file's contents.</param>
public ScriptFile( public ScriptFile(
string filePath, string filePath,
string clientFilePath, string clientUri,
TextReader textReader) TextReader textReader)
{ {
FilePath = filePath; FilePath = filePath;
ClientFilePath = clientFilePath; ClientUri = clientUri;
IsAnalysisEnabled = true; IsAnalysisEnabled = true;
IsInMemory = Workspace.IsPathInMemoryOrNonFileUri(filePath); IsInMemory = Workspace.IsPathInMemoryOrNonFileUri(filePath);
@@ -130,15 +130,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
/// Creates a new ScriptFile instance with the specified file contents. /// Creates a new ScriptFile instance with the specified file contents.
/// </summary> /// </summary>
/// <param name="filePath">The path at which the script file resides.</param> /// <param name="filePath">The path at which the script file resides.</param>
/// <param name="clientFilePath">The path which the client uses to identify the file.</param> /// <param name="clientUri">The path which the client uses to identify the file.</param>
/// <param name="initialBuffer">The initial contents of the script file.</param> /// <param name="initialBuffer">The initial contents of the script file.</param>
public ScriptFile( public ScriptFile(
string filePath, string filePath,
string clientFilePath, string clientUri,
string initialBuffer) string initialBuffer)
{ {
FilePath = filePath; FilePath = filePath;
ClientFilePath = clientFilePath; ClientUri = clientUri;
IsAnalysisEnabled = true; IsAnalysisEnabled = true;
SetFileContents(initialBuffer); SetFileContents(initialBuffer);

View File

@@ -70,7 +70,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
// Resolve the full file path // Resolve the full file path
ResolvedFile resolvedFile = this.ResolveFilePath(filePath); ResolvedFile resolvedFile = this.ResolveFilePath(filePath);
string keyName = resolvedFile.LowercaseFilePath; string keyName = resolvedFile.LowercaseClientUri;
ScriptFile scriptFile = null; ScriptFile scriptFile = null;
return this.workspaceFiles.TryGetValue(keyName, out scriptFile); return this.workspaceFiles.TryGetValue(keyName, out scriptFile);
@@ -98,7 +98,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
// Resolve the full file path // Resolve the full file path
ResolvedFile resolvedFile = this.ResolveFilePath(filePath); ResolvedFile resolvedFile = this.ResolveFilePath(filePath);
string keyName = resolvedFile.LowercaseFilePath; string keyName = resolvedFile.LowercaseClientUri;
// Make sure the file isn't already loaded into the workspace // Make sure the file isn't already loaded into the workspace
ScriptFile scriptFile = null; ScriptFile scriptFile = null;
@@ -117,7 +117,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
using (FileStream fileStream = new FileStream(resolvedFile.FilePath, FileMode.Open, FileAccess.Read)) using (FileStream fileStream = new FileStream(resolvedFile.FilePath, FileMode.Open, FileAccess.Read))
using (StreamReader streamReader = new StreamReader(fileStream, Encoding.UTF8)) using (StreamReader streamReader = new StreamReader(fileStream, Encoding.UTF8))
{ {
scriptFile = new ScriptFile(resolvedFile.FilePath, filePath,streamReader); scriptFile = new ScriptFile(resolvedFile.FilePath, resolvedFile.ClientUri,streamReader);
this.workspaceFiles.Add(keyName, scriptFile); this.workspaceFiles.Add(keyName, scriptFile);
} }
@@ -128,20 +128,26 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
return scriptFile; return scriptFile;
} }
private ResolvedFile ResolveFilePath(string filePath) /// <summary>
/// Resolves a URI identifier into an actual file on disk if it exists.
/// </summary>
/// <param name="clientUri">The URI identifying the file</param>
/// <returns></returns>
private ResolvedFile ResolveFilePath(string clientUri)
{ {
bool canReadFromDisk = false; bool canReadFromDisk = false;
if (!IsPathInMemoryOrNonFileUri(filePath)) string filePath = clientUri;
if (!IsPathInMemoryOrNonFileUri(clientUri))
{ {
if (filePath.StartsWith(@"file://")) if (clientUri.StartsWith(@"file://"))
{ {
// VS Code encodes the ':' character in the drive name, which can lead to problems parsing // VS Code encodes the ':' character in the drive name, which can lead to problems parsing
// the URI, so unencode it if present. See https://github.com/Microsoft/vscode/issues/2990 // the URI, so unencode it if present. See https://github.com/Microsoft/vscode/issues/2990
filePath = filePath.Replace("%3A/", ":/", StringComparison.OrdinalIgnoreCase); clientUri = clientUri.Replace("%3A/", ":/", StringComparison.OrdinalIgnoreCase);
// Client sent the path in URI format, extract the local path and trim // Client sent the path in URI format, extract the local path and trim
// any extraneous slashes // any extraneous slashes
Uri fileUri = new Uri(filePath); Uri fileUri = new Uri(clientUri);
filePath = fileUri.LocalPath; filePath = fileUri.LocalPath;
if (filePath.StartsWith("//") || filePath.StartsWith("\\\\") || filePath.StartsWith("/")) if (filePath.StartsWith("//") || filePath.StartsWith("\\\\") || filePath.StartsWith("/"))
{ {
@@ -154,21 +160,36 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
// into the SqlTools engine. // into the SqlTools engine.
filePath = UnescapePath(filePath); filePath = UnescapePath(filePath);
// Client paths are handled a bit differently because of how we currently identifiers in
// ADS. The URI is passed around as an identifier - but for things we control like connecting
// an editor the URI we pass in is NOT escaped fully. This is a problem for certain functionality
// which is handled by VS Code - such as Intellise Completion - as the URI passed in there is
// the fully escaped URI. That means we need to do some extra work to make sure that the URI values
// are consistent.
// So to solve that we'll make sure to unescape ALL uri's that are passed in and store that value for
// use as an identifier (filePath will be the actual file path on disk).
// # and ? are still always escaped though by ADS so we need to escape those again to get them to actually
// match
clientUri = Uri.UnescapeDataString(UnescapePath(clientUri));
clientUri = clientUri.Replace("#", "%23");
clientUri = clientUri.Replace("?", "%3F");
// switch to unix path separators on non-Windows platforms // switch to unix path separators on non-Windows platforms
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
filePath = filePath.Replace('\\', '/'); filePath = filePath.Replace('\\', '/');
clientUri = clientUri.Replace('\\', '/');
} }
// Get the absolute file path // Get the absolute file path
ResolvedFile resolvedFile = FileUtilities.TryGetFullPath(filePath); ResolvedFile resolvedFile = FileUtilities.TryGetFullPath(filePath, clientUri);
filePath = resolvedFile.FilePath; filePath = resolvedFile.FilePath;
canReadFromDisk = resolvedFile.CanReadFromDisk; canReadFromDisk = resolvedFile.CanReadFromDisk;
} }
Logger.Write(TraceEventType.Verbose, "Resolved path: " + filePath); Logger.Write(TraceEventType.Verbose, "Resolved path: " + clientUri);
return new ResolvedFile(filePath, canReadFromDisk); return new ResolvedFile(filePath, clientUri, canReadFromDisk);
} }
/// <summary> /// <summary>
@@ -204,13 +225,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
// Resolve the full file path // Resolve the full file path
ResolvedFile resolvedFile = this.ResolveFilePath(filePath); ResolvedFile resolvedFile = this.ResolveFilePath(filePath);
string keyName = resolvedFile.LowercaseFilePath; string keyName = resolvedFile.LowercaseClientUri;
// Make sure the file isn't already loaded into the workspace // Make sure the file isn't already loaded into the workspace
ScriptFile scriptFile = null; ScriptFile scriptFile = null;
if (!this.workspaceFiles.TryGetValue(keyName, out scriptFile)) if (!this.workspaceFiles.TryGetValue(keyName, out scriptFile))
{ {
scriptFile = new ScriptFile(resolvedFile.FilePath, filePath, initialBuffer); scriptFile = new ScriptFile(resolvedFile.FilePath, resolvedFile.ClientUri, initialBuffer);
this.workspaceFiles.Add(keyName, scriptFile); this.workspaceFiles.Add(keyName, scriptFile);

View File

@@ -42,7 +42,7 @@ namespace Microsoft.SqlTools.ManagedBatchParser.IntegrationTests.Utility
{ {
ownerUri = GetTestSqlFile(); ownerUri = GetTestSqlFile();
scriptFile = TestServiceProvider.Instance.WorkspaceService.Workspace.GetFile(ownerUri); scriptFile = TestServiceProvider.Instance.WorkspaceService.Workspace.GetFile(ownerUri);
ownerUri = scriptFile.ClientFilePath; ownerUri = scriptFile.ClientUri;
} }
var connectionService = GetLiveTestConnectionService(); var connectionService = GetLiveTestConnectionService();
var connectionResult = var connectionResult =
@@ -68,7 +68,7 @@ namespace Microsoft.SqlTools.ManagedBatchParser.IntegrationTests.Utility
{ {
ownerUri = GetTestSqlFile(); ownerUri = GetTestSqlFile();
scriptFile = TestServiceProvider.Instance.WorkspaceService.Workspace.GetFile(ownerUri); scriptFile = TestServiceProvider.Instance.WorkspaceService.Workspace.GetFile(ownerUri);
ownerUri = scriptFile.ClientFilePath; ownerUri = scriptFile.ClientUri;
} }
ConnectParams connectParams = TestServiceProvider.Instance.ConnectionProfileService.GetConnectionParameters(TestServerType.OnPrem, databaseName); ConnectParams connectParams = TestServiceProvider.Instance.ConnectionProfileService.GetConnectionParameters(TestServerType.OnPrem, databaseName);

View File

@@ -231,7 +231,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
{ {
TextDocument = new TextDocumentIdentifier TextDocument = new TextDocumentIdentifier
{ {
Uri = result.ScriptFile.ClientFilePath Uri = result.ScriptFile.ClientUri
}, },
Position = new Position Position = new Position
{ {
@@ -246,7 +246,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
Thread.Sleep(2000); Thread.Sleep(2000);
// We should get back a non-null ScriptParseInfo // We should get back a non-null ScriptParseInfo
ScriptParseInfo parseInfo = service.GetScriptParseInfo(result.ScriptFile.ClientFilePath); ScriptParseInfo parseInfo = service.GetScriptParseInfo(result.ScriptFile.ClientUri);
Assert.NotNull(parseInfo); Assert.NotNull(parseInfo);
// And we should get back a non-null SignatureHelp // And we should get back a non-null SignatureHelp
@@ -298,7 +298,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
{ {
TextDocument = new TextDocumentIdentifier TextDocument = new TextDocumentIdentifier
{ {
Uri = connectionInfoResult.ScriptFile.ClientFilePath Uri = connectionInfoResult.ScriptFile.ClientUri
}, },
Position = new Position Position = new Position
{ {
@@ -318,7 +318,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
// And refresh the cache // And refresh the cache
await langService.HandleRebuildIntelliSenseNotification( await langService.HandleRebuildIntelliSenseNotification(
new RebuildIntelliSenseParams() { OwnerUri = connectionInfoResult.ScriptFile.ClientFilePath }, new RebuildIntelliSenseParams() { OwnerUri = connectionInfoResult.ScriptFile.ClientUri },
new TestEventContext()); new TestEventContext());
// Now we should expect to see the item show up in the completion list // Now we should expect to see the item show up in the completion list
@@ -342,24 +342,24 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
public async Task HandleRequestToChangeToSqlcmdFile() public async Task HandleRequestToChangeToSqlcmdFile()
{ {
var scriptFile = new ScriptFile() { ClientFilePath = "HandleRequestToChangeToSqlcmdFile_" + DateTime.Now.ToLongDateString() + "_.sql" }; var scriptFile = new ScriptFile() { ClientUri = "HandleRequestToChangeToSqlcmdFile_" + DateTime.Now.ToLongDateString() + "_.sql" };
try try
{ {
// Prepare a script file // Prepare a script file
scriptFile.SetFileContents("koko wants a bananas"); scriptFile.SetFileContents("koko wants a bananas");
File.WriteAllText(scriptFile.ClientFilePath, scriptFile.Contents); File.WriteAllText(scriptFile.ClientUri, scriptFile.Contents);
// Create a workspace and add file to it so that its found for intellense building // Create a workspace and add file to it so that its found for intellense building
var workspace = new ServiceLayer.Workspace.Workspace(); var workspace = new ServiceLayer.Workspace.Workspace();
var workspaceService = new WorkspaceService<SqlToolsSettings> { Workspace = workspace }; var workspaceService = new WorkspaceService<SqlToolsSettings> { Workspace = workspace };
var langService = new LanguageService() { WorkspaceServiceInstance = workspaceService }; var langService = new LanguageService() { WorkspaceServiceInstance = workspaceService };
langService.CurrentWorkspace.GetFile(scriptFile.ClientFilePath); langService.CurrentWorkspace.GetFile(scriptFile.ClientUri);
langService.CurrentWorkspaceSettings.SqlTools.IntelliSense.EnableIntellisense = true; langService.CurrentWorkspaceSettings.SqlTools.IntelliSense.EnableIntellisense = true;
// Add a connection to ensure the intellisense building works // Add a connection to ensure the intellisense building works
ConnectionInfo connectionInfo = GetLiveAutoCompleteTestObjects().ConnectionInfo; ConnectionInfo connectionInfo = GetLiveAutoCompleteTestObjects().ConnectionInfo;
langService.ConnectionServiceInstance.OwnerToConnectionMap.Add(scriptFile.ClientFilePath, connectionInfo); langService.ConnectionServiceInstance.OwnerToConnectionMap.Add(scriptFile.ClientUri, connectionInfo);
// Test SQL // Test SQL
int countOfValidationCalls = 0; int countOfValidationCalls = 0;
@@ -367,7 +367,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
eventContextSql.Setup(x => x.SendEvent(PublishDiagnosticsNotification.Type, It.Is<PublishDiagnosticsNotification>((notif) => ValidateNotification(notif, 2, ref countOfValidationCalls)))).Returns(Task.FromResult(new object())); eventContextSql.Setup(x => x.SendEvent(PublishDiagnosticsNotification.Type, It.Is<PublishDiagnosticsNotification>((notif) => ValidateNotification(notif, 2, ref countOfValidationCalls)))).Returns(Task.FromResult(new object()));
await langService.HandleDidChangeLanguageFlavorNotification(new LanguageFlavorChangeParams await langService.HandleDidChangeLanguageFlavorNotification(new LanguageFlavorChangeParams
{ {
Uri = scriptFile.ClientFilePath, Uri = scriptFile.ClientUri,
Language = LanguageService.SQL_LANG.ToLower(), Language = LanguageService.SQL_LANG.ToLower(),
Flavor = "MSSQL" Flavor = "MSSQL"
}, eventContextSql.Object); }, eventContextSql.Object);
@@ -378,7 +378,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
eventContextSqlCmd.Setup(x => x.SendEvent(PublishDiagnosticsNotification.Type, It.Is<PublishDiagnosticsNotification>((notif) => ValidateNotification(notif, 0, ref countOfValidationCalls)))).Returns(Task.FromResult(new object())); eventContextSqlCmd.Setup(x => x.SendEvent(PublishDiagnosticsNotification.Type, It.Is<PublishDiagnosticsNotification>((notif) => ValidateNotification(notif, 0, ref countOfValidationCalls)))).Returns(Task.FromResult(new object()));
await langService.HandleDidChangeLanguageFlavorNotification(new LanguageFlavorChangeParams await langService.HandleDidChangeLanguageFlavorNotification(new LanguageFlavorChangeParams
{ {
Uri = scriptFile.ClientFilePath, Uri = scriptFile.ClientUri,
Language = LanguageService.SQL_CMD_LANG.ToLower(), Language = LanguageService.SQL_CMD_LANG.ToLower(),
Flavor = "MSSQL" Flavor = "MSSQL"
}, eventContextSqlCmd.Object); }, eventContextSqlCmd.Object);
@@ -388,9 +388,9 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
} }
finally finally
{ {
if (File.Exists(scriptFile.ClientFilePath)) if (File.Exists(scriptFile.ClientUri))
{ {
File.Delete(scriptFile.ClientFilePath); File.Delete(scriptFile.ClientUri);
} }
} }
} }

View File

@@ -235,7 +235,11 @@ GO";
.Callback<string, Func<IBindingContext, CancellationToken, object>, Func<IBindingContext, object>, Func<Exception, object>, int?, int?>( .Callback<string, Func<IBindingContext, CancellationToken, object>, Func<IBindingContext, object>, Func<Exception, object>, int?, int?>(
(key, bindOperation, timeoutOperation, errHandler, t1, t2) => (key, bindOperation, timeoutOperation, errHandler, t1, t2) =>
{ {
timeoutResult = (DefinitionResult)timeoutOperation((IBindingContext)null); if(timeoutOperation != null)
{
timeoutResult = (DefinitionResult)timeoutOperation(null);
}
itemMock.Object.Result = timeoutResult; itemMock.Object.Result = timeoutResult;
}) })
.Returns(() => itemMock.Object); .Returns(() => itemMock.Object);
@@ -251,14 +255,14 @@ GO";
}; };
LiveConnectionHelper.TestConnectionResult connectionResult = LiveConnectionHelper.InitLiveConnectionInfo(); LiveConnectionHelper.TestConnectionResult connectionResult = LiveConnectionHelper.InitLiveConnectionInfo();
ScriptFile scriptFile = connectionResult.ScriptFile; ScriptFile scriptFile = connectionResult.ScriptFile;
ConnectionInfo connInfo = connectionResult.ConnectionInfo;
scriptFile.Contents = "select * from dbo.func ()"; scriptFile.Contents = "select * from dbo.func ()";
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true }; ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
languageService.ScriptParseInfoMap.Add(OwnerUri, scriptInfo); languageService.ScriptParseInfoMap.Add(scriptFile.ClientUri, scriptInfo);
// When I call the language service // Pass in null connection info to force doing a local parse since that hits the BindingQueue timeout
var result = languageService.GetDefinition(textDocument, scriptFile, connInfo); // before we want it to (this is testing the timeout trying to fetch the definitions after the parse)
var result = languageService.GetDefinition(textDocument, scriptFile, null);
// Then I expect null locations and an error to be reported // Then I expect null locations and an error to be reported
Assert.NotNull(result); Assert.NotNull(result);

View File

@@ -44,7 +44,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility
{ {
ownerUri = GetTestSqlFile(); ownerUri = GetTestSqlFile();
scriptFile = TestServiceProvider.Instance.WorkspaceService.Workspace.GetFile(ownerUri); scriptFile = TestServiceProvider.Instance.WorkspaceService.Workspace.GetFile(ownerUri);
ownerUri = scriptFile.ClientFilePath; ownerUri = scriptFile.ClientUri;
} }
var connectionService = GetLiveTestConnectionService(); var connectionService = GetLiveTestConnectionService();
var connectionResult = var connectionResult =
@@ -70,7 +70,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility
{ {
ownerUri = GetTestSqlFile(); ownerUri = GetTestSqlFile();
scriptFile = TestServiceProvider.Instance.WorkspaceService.Workspace.GetFile(ownerUri); scriptFile = TestServiceProvider.Instance.WorkspaceService.Workspace.GetFile(ownerUri);
ownerUri = scriptFile.ClientFilePath; ownerUri = scriptFile.ClientUri;
} }
ConnectParams connectParams = TestServiceProvider.Instance.ConnectionProfileService.GetConnectionParameters(TestServerType.OnPrem, databaseName); ConnectParams connectParams = TestServiceProvider.Instance.ConnectionProfileService.GetConnectionParameters(TestServerType.OnPrem, databaseName);

View File

@@ -3,23 +3,16 @@
// 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.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.SqlServer.Management.SqlParser.Binder;
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Parser; using Microsoft.SqlServer.Management.SqlParser.Parser;
using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.LanguageServices; using Microsoft.SqlTools.ServiceLayer.LanguageServices;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; 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 Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
using GlobalCommon = Microsoft.SqlTools.ServiceLayer.Test.Common;
using Moq; using Moq;
using Xunit; using Xunit;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
{ {
@@ -91,11 +84,24 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
} }
[Fact] [Fact]
public void GetDefinitionInvalidTextDocument() public async void HandleDefinitionRequest_InvalidTextDocument_SendsEmptyListResponse()
{ {
InitializeTestObjects(); InitializeTestObjects();
textDocument.TextDocument.Uri = "invaliduri"; textDocument.TextDocument.Uri = "invaliduri";
Assert.Null(langService.GetDefinition(textDocument, null, null));
// setup the mock for SendResult
var definitionRequestContext = new Mock<RequestContext<Location[]>>();
Location[] result = null;
definitionRequestContext.Setup(rc => rc.SendResult(It.IsAny<Location[]>()))
.Returns<Location[]>((resultDetails) => {
result = resultDetails;
return Task.FromResult(0);
});
await langService.HandleDefinitionRequest(textDocument, definitionRequestContext.Object);
// Should get an empty array when passed
Assert.NotNull(result);
Assert.True(result.Length == 0, $"Unexpected values passed to SendResult : [{ string.Join(",", (object[])result)}]");
} }
[Fact] [Fact]
@@ -113,11 +119,21 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
} }
[Fact] [Fact]
public void GetCompletionItemsInvalidTextDocument() public async void HandleCompletionRequest_InvalidTextDocument_SendsNullResult()
{ {
InitializeTestObjects(); InitializeTestObjects();
// setup the mock for SendResult to capture the items
CompletionItem[] completionItems = null;
requestContext.Setup(x => x.SendResult(It.IsAny<CompletionItem[]>()))
.Returns<CompletionItem[]>((resultDetails) => {
completionItems = resultDetails;
return Task.FromResult(0);
});
textDocument.TextDocument.Uri = "somethinggoeshere"; textDocument.TextDocument.Uri = "somethinggoeshere";
Assert.True(langService.GetCompletionItems(textDocument, scriptFile.Object, null).Result.Length > 0); await langService.HandleCompletionRequest(textDocument, requestContext.Object);
requestContext.Verify(m => m.SendResult(It.IsAny<CompletionItem[]>()), Times.Once());
Assert.Null(completionItems);
} }
[Fact] [Fact]

View File

@@ -69,12 +69,12 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
// set up file for returning the query // set up file for returning the query
scriptFile = new Mock<ScriptFile>(); scriptFile = new Mock<ScriptFile>();
scriptFile.SetupGet(file => file.Contents).Returns(GlobalCommon.Constants.StandardQuery); scriptFile.SetupGet(file => file.Contents).Returns(GlobalCommon.Constants.StandardQuery);
scriptFile.SetupGet(file => file.ClientFilePath).Returns(this.testScriptUri); scriptFile.SetupGet(file => file.ClientUri).Returns(this.testScriptUri);
// set up workspace mock // set up workspace mock
workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>(); workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>())) workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
.Returns(scriptFile.Object); .Returns((string filePath) => { return filePath == this.testScriptUri ? scriptFile.Object : null; });
// setup binding queue mock // setup binding queue mock
bindingQueue = new Mock<ConnectedBindingQueue>(); bindingQueue = new Mock<ConnectedBindingQueue>();