From 5464e4e63a8394d2b46e0ad69211463836c51717 Mon Sep 17 00:00:00 2001 From: Sharon Ravindran Date: Thu, 19 Jan 2017 13:24:58 -0800 Subject: [PATCH] Feature/peek def code gen (#215) * Add codeGen for existing types * Modify code gen logic to match current code * Extend logic for new smo objects * Add logic to retrieve token type from QuickInfo * Remove duplicate types * Add tests for new types * Modify GetScript logic to use suggestions first * Add more tests * Modify codeGen to include quickInfo logic * Cake build changes to run CodeGen * CodeGen replace indentation * Refactor GetScript and add more tests * Refactor Resolver calls * Fix TestDriver test for Definition * Change quickInfo string case * Revert change sto .sln file * Fix typos in comments * change String to string * Rename test sql objects --- build.cake | 18 + scripts/packages.config | 5 +- sqltoolsservice.sln | 2 +- .../LanguageServices/LanguageService.cs | 192 ++++---- .../LanguageServices/PeekDefinition.cs | 429 ++++++++++-------- .../LanguageServices/PeekDefinitionScripts.cs | 199 ++++++++ .../LanguageServices/PeekDefinitionScripts.tt | 144 ++++++ .../PeekDefinitionSupportedTypes.xml | 66 +++ .../LanguageServer/PeekDefinitionTests.cs | 415 ++++++++++++++++- .../LanguageServer/PeekDefinitionTests.cs | 150 +++++- .../LanguageServiceTests.cs | 30 +- 11 files changed, 1336 insertions(+), 314 deletions(-) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinitionScripts.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinitionScripts.tt create mode 100644 src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinitionSupportedTypes.xml diff --git a/build.cake b/build.cake index e3dbf3a8..595c7818 100644 --- a/build.cake +++ b/build.cake @@ -3,11 +3,13 @@ #load "scripts/runhelpers.cake" #load "scripts/archiving.cake" #load "scripts/artifacts.cake" +#tool "nuget:?package=Mono.TextTransform" using System.ComponentModel; using System.Net; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Cake.Common.IO // Basic arguments var target = Argument("target", "Default"); @@ -258,6 +260,7 @@ Task("TestCore") Task("Test") .IsDependentOn("Setup") .IsDependentOn("SRGen") + .IsDependentOn("CodeGen") .IsDependentOn("BuildTest") .Does(() => { @@ -306,6 +309,7 @@ Task("Test") Task("OnlyPublish") .IsDependentOn("Setup") .IsDependentOn("SRGen") + .IsDependentOn("CodeGen") .Does(() => { var project = buildPlan.MainProject; @@ -541,6 +545,20 @@ Task("SRGen") } }); +/// +/// Executes T4Template-based code generators +/// +Task("CodeGen") + .Does(() => +{ + var t4Files = GetFiles(sourceFolder + "/**/*.tt"); + foreach(var t4Template in t4Files) + { + TransformTemplate(t4Template, new TextTransformSettings {}); + } +}); + + /// /// Default Task aliases to Local. /// diff --git a/scripts/packages.config b/scripts/packages.config index ccbbd827..fd0de851 100644 --- a/scripts/packages.config +++ b/scripts/packages.config @@ -1,6 +1,7 @@ - + - + + \ No newline at end of file diff --git a/sqltoolsservice.sln b/sqltoolsservice.sln index b338c5d3..06934702 100644 --- a/sqltoolsservice.sln +++ b/sqltoolsservice.sln @@ -123,4 +123,4 @@ Global {B6F4BECE-82EE-4AB6-99AC-108AEE466274} = {AB9CA2B8-6F70-431C-8A1D-67479D8A7BE4} {E7CF630E-E084-4DA4-BF69-F61BF0A8F5BE} = {AB9CA2B8-6F70-431C-8A1D-67479D8A7BE4} EndGlobalSection -EndGlobal +EndGlobal \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs index 57096897..232bf217 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs @@ -61,8 +61,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices private ParseOptions defaultParseOptions = new ParseOptions( batchSeparator: LanguageService.DefaultBatchSeperator, - isQuotedIdentifierSet: true, - compatibilityLevel: DatabaseCompatibilityLevel.Current, + isQuotedIdentifierSet: true, + compatibilityLevel: DatabaseCompatibilityLevel.Current, transactSqlVersion: TransactSqlVersion.Current); /// @@ -105,13 +105,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices private static readonly Lazy instance = new Lazy(() => new LanguageService()); - private Lazy> scriptParseInfoMap + private Lazy> scriptParseInfoMap = new Lazy>(() => new Dictionary()); /// /// Gets a mapping dictionary for SQL file URIs to ScriptParseInfo objects /// - internal Dictionary ScriptParseInfoMap + internal Dictionary ScriptParseInfoMap { get { @@ -262,20 +262,20 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } 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( textDocumentPosition.TextDocument.Uri); ConnectionInfo connInfo; LanguageService.ConnectionServiceInstance.TryFindConnection( - scriptFile.ClientFilePath, + scriptFile.ClientFilePath, out connInfo); var completionItems = Instance.GetCompletionItems( textDocumentPosition, scriptFile, connInfo); - + await requestContext.SendResult(completionItems); - } + } } /// @@ -322,7 +322,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices LanguageService.ConnectionServiceInstance.TryFindConnection(scriptFile.ClientFilePath, out connInfo); DefinitionResult definitionResult = LanguageService.Instance.GetDefinition(textDocumentPosition, scriptFile, connInfo); if (definitionResult != null) - { + { if (definitionResult.IsErrorResult) { await requestContext.SendError( new DefinitionError { message = definitionResult.Message }); @@ -330,7 +330,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices else { await requestContext.SendResult(definitionResult.Locations); - } + } } } DocumentStatusHelper.SendStatusChange(requestContext, textDocumentPosition, DocumentStatusHelper.DefinitionRequestCompleted); @@ -382,10 +382,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices private static async Task HandleHoverRequest( TextDocumentPosition textDocumentPosition, RequestContext requestContext) - { + { // check if Quick Info hover tooltips are enabled if (WorkspaceService.Instance.CurrentSettings.IsQuickInfoEnabled) - { + { var scriptFile = WorkspaceService.Instance.Workspace.GetFile( textDocumentPosition.TextDocument.Uri); @@ -396,7 +396,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } } - await requestContext.SendResult(new Hover()); + await requestContext.SendResult(new Hover()); } #endregion @@ -404,42 +404,42 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices #region Handlers for Events from Other Services /// - /// Handle the file open notification + /// Handle the file open notification /// /// /// /// public async Task HandleDidOpenTextDocumentNotification( - ScriptFile scriptFile, + ScriptFile scriptFile, EventContext eventContext) { // if not in the preview window and diagnostics are enabled then run diagnostics if (!IsPreviewWindow(scriptFile) && WorkspaceService.Instance.CurrentSettings.IsDiagnositicsEnabled) { - await RunScriptDiagnostics( + await RunScriptDiagnostics( new ScriptFile[] { scriptFile }, - eventContext); + eventContext); } await Task.FromResult(true); } - - /// - /// Handles text document change events - /// - /// + + /// + /// Handles text document change events + /// + /// /// - public async Task HandleDidChangeTextDocumentNotification(ScriptFile[] changedFiles, EventContext eventContext) - { + public async Task HandleDidChangeTextDocumentNotification(ScriptFile[] changedFiles, EventContext eventContext) + { if (WorkspaceService.Instance.CurrentSettings.IsDiagnositicsEnabled) { - await this.RunScriptDiagnostics( - changedFiles.ToArray(), - eventContext); + await this.RunScriptDiagnostics( + changedFiles.ToArray(), + eventContext); } - await Task.FromResult(true); + await Task.FromResult(true); } /// @@ -449,8 +449,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// /// public async Task HandleDidChangeConfigurationNotification( - SqlToolsSettings newSettings, - SqlToolsSettings oldSettings, + SqlToolsSettings newSettings, + SqlToolsSettings oldSettings, EventContext eventContext) { bool oldEnableIntelliSense = oldSettings.SqlTools.IntelliSense.EnableIntellisense; @@ -480,7 +480,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } } } - + #endregion @@ -495,13 +495,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { RemoveScriptParseInfo(ownerUri); - // currently this method is disabled, but we need to reimplement now that the + // currently this method is disabled, but we need to reimplement now that the // implementation of the 'cache' has changed. await Task.FromResult(0); } /// - /// Parses the SQL text and binds it to the SMO metadata provider if connected + /// Parses the SQL text and binds it to the SMO metadata provider if connected /// /// /// @@ -531,7 +531,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices key: parseInfo.ConnectionKey, bindingTimeout: LanguageService.BindingTimeout, bindOperation: (bindingContext, cancelToken) => - { + { try { ParseResult parseResult = Parser.IncrementalParse( @@ -544,8 +544,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices List parseResults = new List(); parseResults.Add(parseResult); bindingContext.Binder.Bind( - parseResults, - connInfo.ConnectionDetails.DatabaseName, + parseResults, + connInfo.ConnectionDetails.DatabaseName, BindMode.Batch); } catch (ConnectionException) @@ -562,9 +562,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } return null; - }); + }); - queueItem.ItemProcessed.WaitOne(); + queueItem.ItemProcessed.WaitOne(); } } catch (Exception ex) @@ -580,9 +580,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } else { - Logger.Write(LogLevel.Warning, "Binding metadata lock timeout in ParseAndBind"); + Logger.Write(LogLevel.Warning, "Binding metadata lock timeout in ParseAndBind"); } - + return parseInfo.ParseResult; } @@ -592,16 +592,16 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// public async Task UpdateLanguageServiceOnConnection(ConnectionInfo info) { - await Task.Run(() => + await Task.Run(() => { ScriptParseInfo scriptInfo = GetScriptParseInfo(info.OwnerUri, createIfNotExists: true); if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout)) { try - { + { scriptInfo.ConnectionKey = this.BindingQueue.AddConnectionContext(info); scriptInfo.IsConnected = true; - + } catch (Exception ex) { @@ -614,7 +614,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices // (Tell Language Service that I am ready with Metadata Provider Object) Monitor.Exit(scriptInfo.BuildingMetadataLock); } - } + } AutoCompleteHelper.PrepopulateCommonMetadata(info, scriptInfo, this.BindingQueue); @@ -658,7 +658,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices key: scriptParseInfo.ConnectionKey, bindingTimeout: LanguageService.BindingTimeout, bindOperation: (bindingContext, cancelToken) => - { + { foreach (var suggestion in scriptParseInfo.CurrentSuggestions) { if (string.Equals(suggestion.Title, completionItem.Label)) @@ -667,25 +667,25 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices completionItem.Documentation = suggestion.Description; break; } - } - return completionItem; + } + return completionItem; }); - queueItem.ItemProcessed.WaitOne(); + queueItem.ItemProcessed.WaitOne(); } catch (Exception ex) { - // if any exceptions are raised looking up extended completion metadata + // if any exceptions are raised looking up extended completion metadata // then just return the original completion item - Logger.Write(LogLevel.Error, "Exeception in ResolveCompletionItem " + ex.ToString()); - } + Logger.Write(LogLevel.Error, "Exception in ResolveCompletionItem " + ex.ToString()); + } finally { - Monitor.Exit(scriptParseInfo.BuildingMetadataLock); - } + Monitor.Exit(scriptParseInfo.BuildingMetadataLock); + } } } - + return completionItem; } @@ -725,25 +725,21 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { try { - // Queue the task with the binding queue + // Queue the task with the binding queue QueueItem queueItem = this.BindingQueue.QueueBindingOperation( key: scriptParseInfo.ConnectionKey, bindingTimeout: LanguageService.PeekDefinitionTimeout, bindOperation: (bindingContext, cancelToken) => { - // Get suggestions for the token - int parserLine = textDocumentPosition.Position.Line + 1; - int parserColumn = textDocumentPosition.Position.Character + 1; - IEnumerable declarationItems = Resolver.FindCompletions( - scriptParseInfo.ParseResult, - parserLine, parserColumn, - bindingContext.MetadataDisplayInfoProvider); - - // Match token with the suggestions(declaration items) returned - string schemaName = this.GetSchemaName(scriptParseInfo, textDocumentPosition.Position, scriptFile); + // Script object using SMO PeekDefinition peekDefinition = new PeekDefinition(bindingContext.ServerConnection, connInfo); - return peekDefinition.GetScript(declarationItems, tokenText, schemaName); + return peekDefinition.GetScript( + scriptParseInfo.ParseResult, + textDocumentPosition.Position, + bindingContext.MetadataDisplayInfoProvider, + tokenText, + schemaName); }, timeoutOperation: (bindingContext) => { @@ -813,7 +809,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices Token schemaToken = scriptParseInfo.ParseResult.Script.TokenManager.GetToken(schemaTokenIndex); return TextUtilities.RemoveSquareBracketSyntax(schemaToken.Text); } - } + } // if no schema name, returns null return null; } @@ -827,10 +823,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { int startLine = textDocumentPosition.Position.Line; int startColumn = TextUtilities.PositionOfPrevDelimeter( - scriptFile.Contents, + scriptFile.Contents, textDocumentPosition.Position.Line, textDocumentPosition.Position.Character); - int endColumn = textDocumentPosition.Position.Character; + int endColumn = textDocumentPosition.Position.Character; ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri); if (scriptParseInfo != null && scriptParseInfo.ParseResult != null) @@ -843,29 +839,29 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices key: scriptParseInfo.ConnectionKey, bindingTimeout: LanguageService.HoverTimeout, bindOperation: (bindingContext, cancelToken) => - { + { // get the current quick info text Babel.CodeObjectQuickInfo quickInfo = Resolver.GetQuickInfo( - scriptParseInfo.ParseResult, - startLine + 1, - endColumn + 1, + scriptParseInfo.ParseResult, + startLine + 1, + endColumn + 1, bindingContext.MetadataDisplayInfoProvider); - + // convert from the parser format to the VS Code wire format return AutoCompleteHelper.ConvertQuickInfoToHover( - quickInfo, + quickInfo, startLine, - startColumn, + startColumn, endColumn); }); - - queueItem.ItemProcessed.WaitOne(); - return queueItem.GetResultAsT(); + + queueItem.ItemProcessed.WaitOne(); + return queueItem.GetResultAsT(); } finally { Monitor.Exit(scriptParseInfo.BuildingMetadataLock); - } + } } } @@ -880,7 +876,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { int startLine = textDocumentPosition.Position.Line; int startColumn = TextUtilities.PositionOfPrevDelimeter( - scriptFile.Contents, + scriptFile.Contents, textDocumentPosition.Position.Line, textDocumentPosition.Position.Character); int endColumn = textDocumentPosition.Position.Character; @@ -895,12 +891,12 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices ConnectionInfo connInfo; LanguageService.ConnectionServiceInstance.TryFindConnection( - scriptFile.ClientFilePath, + scriptFile.ClientFilePath, out connInfo); // reparse and bind the SQL statement if needed if (RequiresReparse(scriptParseInfo, scriptFile)) - { + { ParseAndBind(scriptFile, connInfo); } @@ -914,14 +910,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices key: scriptParseInfo.ConnectionKey, bindingTimeout: LanguageService.BindingTimeout, bindOperation: (bindingContext, cancelToken) => - { + { // get the list of possible current methods for signature help var methods = Resolver.FindMethods( - scriptParseInfo.ParseResult, - startLine + 1, - endColumn + 1, + scriptParseInfo.ParseResult, + startLine + 1, + endColumn + 1, bindingContext.MetadataDisplayInfoProvider); - + // get positional information on the current method var methodLocations = Resolver.GetMethodNameAndParams(scriptParseInfo.ParseResult, startLine + 1, @@ -941,14 +937,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices return null; } }); - - queueItem.ItemProcessed.WaitOne(); - return queueItem.GetResultAsT(); + + queueItem.ItemProcessed.WaitOne(); + return queueItem.GetResultAsT(); } finally { Monitor.Exit(scriptParseInfo.BuildingMetadataLock); - } + } } } @@ -974,14 +970,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices // get the current script parse info object ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri); - + if (scriptParseInfo == null) { return AutoCompleteHelper.GetDefaultCompletionItems(ScriptDocumentInfo.CreateDefaultDocumentInfo(textDocumentPosition, scriptFile), useLowerCaseSuggestions); } ScriptDocumentInfo scriptDocumentInfo = new ScriptDocumentInfo(textDocumentPosition, scriptFile, scriptParseInfo); - + // reparse and bind the SQL statement if needed if (RequiresReparse(scriptParseInfo, scriptFile)) { @@ -1019,9 +1015,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { ConnectionInfo connInfo; ConnectionService.Instance.TryFindConnection( - scriptFile.ClientFilePath, + scriptFile.ClientFilePath, out connInfo); - + var parseResult = ParseAndBind(scriptFile, connInfo); // build a list of SQL script file markers from the errors @@ -1181,7 +1177,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// Internal for testing purposes only /// /// - /// Creates a new instance if one doesn't exist + /// Creates a new instance if one doesn't exist internal ScriptParseInfo GetScriptParseInfo(string uri, bool createIfNotExists = false) { lock (this.parseMapLock) @@ -1209,7 +1205,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices lock (this.parseMapLock) { if (this.ScriptParseInfoMap.ContainsKey(uri)) - { + { return this.ScriptParseInfoMap.Remove(uri); } else diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinition.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinition.cs index d6456307..5c6d2e58 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinition.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinition.cs @@ -4,17 +4,21 @@ // using System; using System.IO; +using System.Linq; using System.Collections.Generic; using System.Collections.Specialized; using System.Data.SqlClient; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.SqlParser.Intellisense; +using Microsoft.SqlServer.Management.SqlParser.Parser; +using Microsoft.SqlServer.Management.SqlParser.MetadataProvider; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; +using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location; namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { @@ -22,7 +26,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// Peek Definition/ Go to definition implementation /// Script sql objects and write create scripts to file /// - internal class PeekDefinition + internal partial class PeekDefinition { private bool error; private string errorMessage; @@ -37,9 +41,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices private Dictionary sqlScriptGetters = new Dictionary(); + private Dictionary sqlScriptGettersFromQuickInfo = + new Dictionary(); + // Dictionary that holds the object name (as appears on the TSQL create statement) private Dictionary sqlObjectTypes = new Dictionary(); + private Dictionary sqlObjectTypesFromQuickInfo = new Dictionary(); + /// /// Initialize a Peek Definition helper object /// @@ -48,7 +57,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { this.serverConnection = serverConnection; this.connectionInfo = connInfo; - this.tempPath = FileUtils.GetPeekDefinitionTempFolder(); + this.tempPath = FileUtils.GetPeekDefinitionTempFolder(); Initialize(); } @@ -70,7 +79,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { Logger.Write(LogLevel.Error, "Exception at PeekDefinition Database.get() : " + cfe.Message); this.error = true; - this.errorMessage = (connectionInfo != null && connectionInfo.IsAzure)? SR.PeekDefinitionAzureError(cfe.Message) : SR.PeekDefinitionError(cfe.Message); + this.errorMessage = (connectionInfo != null && connectionInfo.IsAzure) ? SR.PeekDefinitionAzureError(cfe.Message) : SR.PeekDefinitionError(cfe.Message); return null; } catch (Exception ex) @@ -81,78 +90,23 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices return null; } } - } return this.database; } } - /// - /// Add getters for each sql object supported by peek definition - /// - private void Initialize() - { - //Add script getters for each sql object - - //Add tables to supported types - AddSupportedType(DeclarationType.Table, GetTableScripts, "Table"); - - //Add views to supported types - AddSupportedType(DeclarationType.View, GetViewScripts, "view"); - - //Add stored procedures to supported types - AddSupportedType(DeclarationType.StoredProcedure, GetStoredProcedureScripts, "Procedure"); - } - /// /// Add the given type, scriptgetter and the typeName string to the respective dictionaries /// - private void AddSupportedType(DeclarationType type, ScriptGetter scriptGetter, string typeName) + private void AddSupportedType(DeclarationType type, ScriptGetter scriptGetter, string typeName, string quickInfoType) { sqlScriptGetters.Add(type, scriptGetter); sqlObjectTypes.Add(type, typeName); - - } - - /// - /// Convert a file to a location array containing a location object as expected by the extension - /// - internal Location[] GetLocationFromFile(string tempFileName, int lineNumber) - { - if (Path.DirectorySeparatorChar.Equals('/')) + if (!string.IsNullOrEmpty(quickInfoType)) { - tempFileName = "file:" + tempFileName; + sqlScriptGettersFromQuickInfo.Add(quickInfoType.ToLowerInvariant(), scriptGetter); + sqlObjectTypesFromQuickInfo.Add(quickInfoType.ToLowerInvariant(), typeName); } - else - { - tempFileName = new Uri(tempFileName).AbsoluteUri; - } - Location[] locations = new[] { - new Location { - Uri = tempFileName, - Range = new Range { - Start = new Position { Line = lineNumber, Character = 1}, - End = new Position { Line = lineNumber + 1, Character = 1} - } - } - }; - return locations; - } - - /// - /// Get line number for the create statement - /// - private int GetStartOfCreate(string script, string createString) - { - string[] lines = script.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); - for (int lineNumber = 0; lineNumber < lines.Length; lineNumber++) - { - if (lines[lineNumber].IndexOf(createString, StringComparison.OrdinalIgnoreCase) >= 0) - { - return lineNumber; - } - } - return 0; } /// @@ -162,45 +116,77 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// /// /// Location object of the script file - internal DefinitionResult GetScript(IEnumerable declarationItems, string tokenText, string schemaName) + internal DefinitionResult GetScript(ParseResult parseResult, Position position, IMetadataDisplayInfoProvider metadataDisplayInfoProvider, string tokenText, string schemaName) { - foreach (Declaration declarationItem in declarationItems) + int parserLine = position.Line + 1; + int parserColumn = position.Character + 1; + // Get DeclarationItems from The Intellisense Resolver for the selected token. The type of the selected token is extracted from the declarationItem. + IEnumerable declarationItems = GetCompletionsForToken(parseResult, parserLine, parserColumn, metadataDisplayInfoProvider); + if (declarationItems != null && declarationItems.Count() > 0) { - if (declarationItem.Title == null) + foreach (Declaration declarationItem in declarationItems) { - continue; - } - - if (declarationItem.Title.Equals(tokenText)) - { - // Script object using SMO based on type - DeclarationType type = declarationItem.Type; - if (sqlScriptGetters.ContainsKey(type) && sqlObjectTypes.ContainsKey(type)) + if (declarationItem.Title == null) { - // On *nix and mac systems, the defaultSchema property throws an Exception when accessed. - // This workaround ensures that a schema name is present by attempting - // to get the schema name from the declaration item - // If all fails, the default schema name is assumed to be "dbo" - if ((connectionInfo != null && connectionInfo.ConnectionDetails.AuthenticationType.Equals(Constants.SqlLoginAuthenticationType)) && string.IsNullOrEmpty(schemaName)) - { - string fullObjectName = declarationItem.DatabaseQualifiedName; - schemaName = this.GetSchemaFromDatabaseQualifiedName(fullObjectName, tokenText); - } - Location[] locations = GetSqlObjectDefinition( - sqlScriptGetters[type], - tokenText, - schemaName, - sqlObjectTypes[type] - ); - DefinitionResult result = new DefinitionResult - { - IsErrorResult = this.error, - Message = this.errorMessage, - Locations = locations - }; - return result; + continue; } - // sql object type is currently not supported + // if declarartionItem matches the selected token, script SMO using that type + if (declarationItem.Title.Equals(tokenText)) + { + return GetDefinitionUsingDeclarationType(declarationItem.Type, declarationItem.DatabaseQualifiedName, tokenText, schemaName); + } + } + } + else + { + // if no declarationItem matched the selected token, we try to find the type of the token using QuickInfo.Text + string quickInfoText = GetQuickInfoForToken(parseResult, parserLine, parserColumn, metadataDisplayInfoProvider); + return GetDefinitionUsingQuickInfoText(quickInfoText, tokenText, schemaName); + } + // no definition found + return GetDefinitionErrorResult(SR.PeekDefinitionNoResultsError); + } + + /// + /// Script an object using the type extracted from quickInfo Text + /// + /// the text from the quickInfo for the selected token + /// The text of the selected token + /// Schema name + /// + internal DefinitionResult GetDefinitionUsingQuickInfoText(string quickInfoText, string tokenText, string schemaName) + { + string tokenType = GetTokenTypeFromQuickInfo(quickInfoText, tokenText); + if (tokenType != null) + { + if (sqlScriptGettersFromQuickInfo.ContainsKey(tokenType.ToLowerInvariant())) + { + // With SqlLogin authentication, the defaultSchema property throws an Exception when accessed. + // This workaround ensures that a schema name is present by attempting + // to get the schema name from the declaration item. + // If all fails, the default schema name is assumed to be "dbo" + if ((connectionInfo != null && connectionInfo.ConnectionDetails.AuthenticationType.Equals(Constants.SqlLoginAuthenticationType)) && string.IsNullOrEmpty(schemaName)) + { + string fullObjectName = this.GetFullObjectNameFromQuickInfo(quickInfoText, tokenText); + schemaName = this.GetSchemaFromDatabaseQualifiedName(fullObjectName, tokenText); + } + Location[] locations = GetSqlObjectDefinition( + sqlScriptGettersFromQuickInfo[tokenType.ToLowerInvariant()], + tokenText, + schemaName, + sqlObjectTypesFromQuickInfo[tokenType.ToLowerInvariant()] + ); + DefinitionResult result = new DefinitionResult + { + IsErrorResult = this.error, + Message = this.errorMessage, + Locations = locations + }; + return result; + } + else + { + // If a type was found but is not in sqlScriptGettersFromQuickInfo, then the type is not supported return GetDefinitionErrorResult(SR.PeekDefinitionTypeNotSupportedError); } } @@ -209,97 +195,41 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } /// - /// Return schema name from the full name of the database. If schema is missing return dbo as schema name. + /// Script a object using the type extracted from declarationItem /// - /// The full database qualified name(database.schema.object) - /// Object name - /// Schema name - internal string GetSchemaFromDatabaseQualifiedName(string fullObjectName, string objectName) - { - string[] tokens = fullObjectName.Split('.'); - for (int i = tokens.Length - 1; i > 0; i--) - { - if (tokens[i].Equals(objectName)) - { - return tokens[i - 1]; - } - } - return "dbo"; - } - - /// - /// Script a table using SMO - /// - /// Table name + /// The Declarartion object that matched with the selected token + /// The text of the selected token /// Schema name - /// String collection of scripts - internal StringCollection GetTableScripts(string tableName, string schemaName) + /// + internal DefinitionResult GetDefinitionUsingDeclarationType(DeclarationType type, string databaseQualifiedName, string tokenText, string schemaName) { - try + if (sqlScriptGetters.ContainsKey(type) && sqlObjectTypes.ContainsKey(type)) { - Table table = string.IsNullOrEmpty(schemaName) - ? new Table(this.Database, tableName) - : new Table(this.Database, tableName, schemaName); - - table.Refresh(); - - return table.Script(); - } - catch (Exception ex) - { - Logger.Write(LogLevel.Error, "Exception at PeekDefinition GetTableScripts : " + ex.Message); - return null; - } - } - - /// - /// Script a view using SMO - /// - /// View name - /// Schema name - /// String collection of scripts - internal StringCollection GetViewScripts(string viewName, string schemaName) - { - try - { - View view = string.IsNullOrEmpty(schemaName) - ? new View(this.Database, viewName) - : new View(this.Database, viewName, schemaName); - - view.Refresh(); - - return view.Script(); - } - catch (Exception ex) - { - Logger.Write(LogLevel.Error, "Exception at PeekDefinition GetViewScripts : " + ex.Message); - return null; - } - } - - /// - /// Script a stored procedure using SMO - /// - /// Stored Procedure name - /// Schema Name - /// String collection of scripts - internal StringCollection GetStoredProcedureScripts(string sprocName, string schemaName) - { - try - { - StoredProcedure sproc = string.IsNullOrEmpty(schemaName) - ? new StoredProcedure(this.Database, sprocName) - : new StoredProcedure(this.Database, sprocName, schemaName); - - sproc.Refresh(); - - return sproc.Script(); - } - catch (Exception ex) - { - Logger.Write(LogLevel.Error, "Exception at PeekDefinition GetStoredProcedureScripts : " + ex.Message); - return null; + // With SqlLogin authentication, the defaultSchema property throws an Exception when accessed. + // This workaround ensures that a schema name is present by attempting + // to get the schema name from the declaration item. + // If all fails, the default schema name is assumed to be "dbo" + if ((connectionInfo != null && connectionInfo.ConnectionDetails.AuthenticationType.Equals(Constants.SqlLoginAuthenticationType)) && string.IsNullOrEmpty(schemaName)) + { + string fullObjectName = databaseQualifiedName; + schemaName = this.GetSchemaFromDatabaseQualifiedName(fullObjectName, tokenText); + } + Location[] locations = GetSqlObjectDefinition( + sqlScriptGetters[type], + tokenText, + schemaName, + sqlObjectTypes[type] + ); + DefinitionResult result = new DefinitionResult + { + IsErrorResult = this.error, + Message = this.errorMessage, + Locations = locations + }; + return result; } + // If a type was found but is not in sqlScriptGetters, then the type is not supported + return GetDefinitionErrorResult(SR.PeekDefinitionTypeNotSupportedError); } /// @@ -346,6 +276,71 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } } + #region Helper Methods + /// + /// Return schema name from the full name of the database. If schema is missing return dbo as schema name. + /// + /// The full database qualified name(database.schema.object) + /// Object name + /// Schema name + internal string GetSchemaFromDatabaseQualifiedName(string fullObjectName, string objectName) + { + if(!string.IsNullOrEmpty(fullObjectName)) + { + string[] tokens = fullObjectName.Split('.'); + for (int i = tokens.Length - 1; i > 0; i--) + { + if (tokens[i].Equals(objectName)) + { + return tokens[i - 1]; + } + } + } + return "dbo"; + } + + /// + /// Convert a file to a location array containing a location object as expected by the extension + /// + internal Location[] GetLocationFromFile(string tempFileName, int lineNumber) + { + // Get absolute Uri based on uri format. This works around a dotnetcore URI bug for linux paths. + if (Path.DirectorySeparatorChar.Equals('/')) + { + tempFileName = "file:" + tempFileName; + } + else + { + tempFileName = new Uri(tempFileName).AbsoluteUri; + } + // Create a location array containing the tempFile Uri, as expected by VSCode. + Location[] locations = new[] { + new Location { + Uri = tempFileName, + Range = new Range { + Start = new Position { Line = lineNumber, Character = 1}, + End = new Position { Line = lineNumber + 1, Character = 1} + } + } + }; + return locations; + } + + /// + /// Get line number for the create statement + /// + private int GetStartOfCreate(string script, string createString) + { + string[] lines = script.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); + for (int lineNumber = 0; lineNumber < lines.Length; lineNumber++) + { + if (lines[lineNumber].IndexOf(createString, StringComparison.OrdinalIgnoreCase) >= 0) + { + return lineNumber; + } + } + return 0; + } /// /// Helper method to create definition error result object /// @@ -360,5 +355,75 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices Locations = null }; } + + /// + /// Return full object name(database.schema.objectName) from the quickInfo text("type database.schema.objectName") + /// + /// QuickInfo Text for this token + /// Token Text + /// + internal string GetFullObjectNameFromQuickInfo(string quickInfoText, string tokenText) + { + if (string.IsNullOrEmpty(quickInfoText) || string.IsNullOrEmpty(tokenText)) + { + return null; + } + // extract full object name from quickInfo text + string[] tokens = quickInfoText.Split(' '); + List tokenList = tokens.Where(el => el.Contains(tokenText)).ToList(); + return (tokenList?.Count() > 0) ? tokenList[0] : null; + } + + /// + /// Return token type from the quickInfo text("type database.schema.objectName") + /// + /// QuickInfo Text for this token + /// + /// + internal string GetTokenTypeFromQuickInfo(string quickInfoText, string tokenText) + { + if (string.IsNullOrEmpty(quickInfoText) || string.IsNullOrEmpty(tokenText)) + { + return null; + } + // extract string denoting the token type from quickInfo text + string[] tokens = quickInfoText.Split(' '); + List indexList = tokens.Select((s, i) => new { i, s }).Where(el => (el.s).Contains(tokenText)).Select(el => el.i).ToList(); + return (indexList?.Count() > 0) ? String.Join(" ", tokens.Take(indexList[0])) : null; + } + + + /// + /// Wrapper method that calls Resolver.GetQuickInfo + /// + internal string GetQuickInfoForToken(ParseResult parseResult, int parserLine, int parserColumn, IMetadataDisplayInfoProvider metadataDisplayInfoProvider) + { + if (parseResult == null || metadataDisplayInfoProvider == null) + { + return null; + } + Babel.CodeObjectQuickInfo quickInfo = Resolver.GetQuickInfo( + parseResult, parserLine, parserColumn, metadataDisplayInfoProvider); + return quickInfo?.Text; + } + + /// + /// Wrapper method that calls Resolver.FindCompletions + /// + /// + /// + /// + /// + /// + internal IEnumerable GetCompletionsForToken(ParseResult parseResult, int parserLine, int parserColumn, IMetadataDisplayInfoProvider metadataDisplayInfoProvider) + { + if (parseResult == null || metadataDisplayInfoProvider == null) + { + return null; + } + return Resolver.FindCompletions( + parseResult, parserLine, parserColumn, metadataDisplayInfoProvider); + } + #endregion } } \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinitionScripts.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinitionScripts.cs new file mode 100644 index 00000000..ca20816d --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinitionScripts.cs @@ -0,0 +1,199 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +using System; +using System.IO; +using System.Collections.Generic; +using System.Collections.Specialized; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlServer.Management.SqlParser.Intellisense; +using Microsoft.SqlTools.ServiceLayer.Utility; +namespace Microsoft.SqlTools.ServiceLayer.LanguageServices +{ + internal partial class PeekDefinition + { + + private void Initialize() + { + AddSupportedType(DeclarationType.Table, GetTableScripts, "Table", "table"); + AddSupportedType(DeclarationType.View, GetViewScripts, "View", "view"); + AddSupportedType(DeclarationType.StoredProcedure, GetStoredProcedureScripts, "Procedure", "stored procedure"); + AddSupportedType(DeclarationType.UserDefinedDataType, GetUserDefinedDataTypeScripts, "Type", "user-defined data type"); + AddSupportedType(DeclarationType.UserDefinedTableType, GetUserDefinedTableTypeScripts, "Type", "user-defined table type"); + AddSupportedType(DeclarationType.Synonym, GetSynonymScripts, "Synonym", ""); + AddSupportedType(DeclarationType.ScalarValuedFunction, GetScalarValuedFunctionScripts, "Function", "scalar-valued function"); + AddSupportedType(DeclarationType.TableValuedFunction, GetTableValuedFunctionScripts, "Function", "table-valued function"); + } + + /// + /// Script a Table using SMO + /// + /// Table name + /// Schema name + /// String collection of scripts + internal StringCollection GetTableScripts(string objectName, string schemaName) + { + try + { + Table smoObject = string.IsNullOrEmpty(schemaName) ? new Table(this.Database, objectName) : new Table(this.Database, objectName, schemaName); + smoObject.Refresh(); + return smoObject.Script(); + } + catch (Exception ex) + { + Logger.Write(LogLevel.Error,"Exception at PeekDefinition GetTableScripts : " + ex.Message); + return null; + } + } + + /// + /// Script a View using SMO + /// + /// View name + /// Schema name + /// String collection of scripts + internal StringCollection GetViewScripts(string objectName, string schemaName) + { + try + { + View smoObject = string.IsNullOrEmpty(schemaName) ? new View(this.Database, objectName) : new View(this.Database, objectName, schemaName); + smoObject.Refresh(); + return smoObject.Script(); + } + catch (Exception ex) + { + Logger.Write(LogLevel.Error,"Exception at PeekDefinition GetViewScripts : " + ex.Message); + return null; + } + } + + /// + /// Script a StoredProcedure using SMO + /// + /// StoredProcedure name + /// Schema name + /// String collection of scripts + internal StringCollection GetStoredProcedureScripts(string objectName, string schemaName) + { + try + { + StoredProcedure smoObject = string.IsNullOrEmpty(schemaName) ? new StoredProcedure(this.Database, objectName) : new StoredProcedure(this.Database, objectName, schemaName); + smoObject.Refresh(); + return smoObject.Script(); + } + catch (Exception ex) + { + Logger.Write(LogLevel.Error,"Exception at PeekDefinition GetStoredProcedureScripts : " + ex.Message); + return null; + } + } + + /// + /// Script a UserDefinedDataType using SMO + /// + /// UserDefinedDataType name + /// Schema name + /// String collection of scripts + internal StringCollection GetUserDefinedDataTypeScripts(string objectName, string schemaName) + { + try + { + UserDefinedDataType smoObject = string.IsNullOrEmpty(schemaName) ? new UserDefinedDataType(this.Database, objectName) : new UserDefinedDataType(this.Database, objectName, schemaName); + smoObject.Refresh(); + return smoObject.Script(); + } + catch (Exception ex) + { + Logger.Write(LogLevel.Error,"Exception at PeekDefinition GetUserDefinedDataTypeScripts : " + ex.Message); + return null; + } + } + + /// + /// Script a UserDefinedTableType using SMO + /// + /// UserDefinedTableType name + /// Schema name + /// String collection of scripts + internal StringCollection GetUserDefinedTableTypeScripts(string objectName, string schemaName) + { + try + { + UserDefinedTableType smoObject = string.IsNullOrEmpty(schemaName) ? new UserDefinedTableType(this.Database, objectName) : new UserDefinedTableType(this.Database, objectName, schemaName); + smoObject.Refresh(); + return smoObject.Script(); + } + catch (Exception ex) + { + Logger.Write(LogLevel.Error,"Exception at PeekDefinition GetUserDefinedTableTypeScripts : " + ex.Message); + return null; + } + } + + /// + /// Script a Synonym using SMO + /// + /// Synonym name + /// Schema name + /// String collection of scripts + internal StringCollection GetSynonymScripts(string objectName, string schemaName) + { + try + { + Synonym smoObject = string.IsNullOrEmpty(schemaName) ? new Synonym(this.Database, objectName) : new Synonym(this.Database, objectName, schemaName); + smoObject.Refresh(); + return smoObject.Script(); + } + catch (Exception ex) + { + Logger.Write(LogLevel.Error,"Exception at PeekDefinition GetSynonymScripts : " + ex.Message); + return null; + } + } + + /// + /// Script a ScalarValuedFunction using SMO + /// + /// ScalarValuedFunction name + /// Schema name + /// String collection of scripts + internal StringCollection GetScalarValuedFunctionScripts(string objectName, string schemaName) + { + try + { + UserDefinedFunction smoObject = string.IsNullOrEmpty(schemaName) ? new UserDefinedFunction(this.Database, objectName) : new UserDefinedFunction(this.Database, objectName, schemaName); + smoObject.Refresh(); + return smoObject.Script(); + } + catch (Exception ex) + { + Logger.Write(LogLevel.Error,"Exception at PeekDefinition GetScalarValuedFunctionScripts : " + ex.Message); + return null; + } + } + + /// + /// Script a TableValuedFunction using SMO + /// + /// TableValuedFunction name + /// Schema name + /// String collection of scripts + internal StringCollection GetTableValuedFunctionScripts(string objectName, string schemaName) + { + try + { + UserDefinedFunction smoObject = string.IsNullOrEmpty(schemaName) ? new UserDefinedFunction(this.Database, objectName) : new UserDefinedFunction(this.Database, objectName, schemaName); + smoObject.Refresh(); + return smoObject.Script(); + } + catch (Exception ex) + { + Logger.Write(LogLevel.Error,"Exception at PeekDefinition GetTableValuedFunctionScripts : " + ex.Message); + return null; + } + } + + } +} + \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinitionScripts.tt b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinitionScripts.tt new file mode 100644 index 00000000..ac72a0b5 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinitionScripts.tt @@ -0,0 +1,144 @@ +<#@ template debug="false" hostspecific="true" language="C#" #> +<#@ output extension=".cs" #> +<#@ assembly name="System.Xml.dll" #> +<#@ import namespace="System" #> +<#@ import namespace="System.Globalization" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Xml" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.IO" #> +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +using System; +using System.IO; +using System.Collections.Generic; +using System.Collections.Specialized; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlServer.Management.SqlParser.Intellisense; +using Microsoft.SqlTools.ServiceLayer.Utility; +namespace Microsoft.SqlTools.ServiceLayer.LanguageServices +{ + internal partial class PeekDefinition + { + + <# + /// + /// Generate Initialize method + /// + var indent = " "; + var directory = Path.GetDirectoryName(Host.TemplateFile); + string xmlFile = Path.Combine(directory, "PeekDefinitionSupportedTypes.xml"); + var supportedTypes = GetSupportedTypes(xmlFile); + if (supportedTypes != null && supportedTypes.Count > 0) + { + WriteLine("private void Initialize()"); + PushIndent(indent); + PushIndent(indent); + WriteLine("{"); + PushIndent(indent); + foreach(var typeProperty in supportedTypes) + { + string functionCall = string.Format("AddSupportedType(DeclarationType.{0}, Get{0}Scripts, \"{1}\", \"{2}\");", typeProperty["Name"], typeProperty["CreateSyntax"], typeProperty["QuickInfoType"]); + WriteLine(functionCall); + } + PopIndent(); + WriteLine("}\n"); + + /// + /// Generate scriptGetters for each type + /// + + foreach(var typeProperty in supportedTypes) + { + string statement; + // Write comments + WriteLine("/// "); + WriteLine(string.Format("/// Script a {0} using SMO", typeProperty["Name"])); + WriteLine("/// "); + WriteLine(string.Format("/// {0} name", typeProperty["Name"])); + WriteLine(string.Format("/// Schema name")); + WriteLine("/// String collection of scripts"); + + WriteLine(string.Format("internal StringCollection Get{0}Scripts(string objectName, string schemaName)", typeProperty["Name"])); + WriteLine("{"); + PushIndent(indent); + + // Write try block to retrieve object and return script + WriteLine("try"); + WriteLine("{"); + if(typeProperty["SupportsSchemaQuery"].IndexOf("true", StringComparison.OrdinalIgnoreCase) >= 0) + { + statement = string.Format("{0} smoObject = string.IsNullOrEmpty(schemaName) ? new {0}(this.Database, objectName) : new {0}(this.Database, objectName, schemaName);", typeProperty["AccessClass"]); + } + else + { + statement = string.Format("{0} smoObject = new {0}(this.Database, objectName);", typeProperty["Name"]); + } + PushIndent(indent); + WriteLine(statement); + WriteLine("smoObject.Refresh();"); + WriteLine("return smoObject.Script();"); + PopIndent(); + WriteLine("}"); + + // Write catch block to catch and log exceptions + WriteLine("catch (Exception ex)"); + WriteLine("{"); + PushIndent(indent); + statement = string.Format("LogLevel.Error,\"Exception at PeekDefinition Get{0}Scripts : \" + ex.Message", typeProperty["Name"]); + WriteLine("Logger.Write(" + statement + ");"); + WriteLine("return null;"); + PopIndent(); + WriteLine("}"); + PopIndent(); + WriteLine("}\n"); + } + } + PopIndent(); + PopIndent(); + #> + } +} + <#+ + /// + /// Get the supported types from the xml file + /// + public static List> GetSupportedTypes(string xmlFile) + { + List> typeList = null; + XmlDocument doc = new XmlDocument(); + doc.Load(xmlFile); + XmlNodeList supportedTypes = doc.SelectNodes("/SupportedTypes/Type"); + if (supportedTypes != null) + { + typeList = new List>(); + foreach (var type in supportedTypes) + { + XmlElement node = type as XmlElement; + if (node != null) + { + string typeName = (node["Name"] != null) ? node["Name"].InnerText : null; + string createSyntax = (node["CreateSyntax"] != null) ? node["CreateSyntax"].InnerText : null; + string accessClass = (node["AccessClass"] != null) ? node["AccessClass"].InnerText : null; + string supportsSchemaQuery = (node["SupportsSchemaQuery"] != null) ? node["SupportsSchemaQuery"].InnerText : null; + string quickInfoType = (node["QuickInfoType"] != null) ? node["QuickInfoType"].InnerText : null; + if (typeName != null && createSyntax != null && accessClass != null && supportsSchemaQuery!= null) + { + Dictionary typeProperties = new Dictionary(); + typeProperties.Add("Name", typeName); + typeProperties.Add("CreateSyntax", createSyntax); + typeProperties.Add("AccessClass", accessClass); + typeProperties.Add("SupportsSchemaQuery", supportsSchemaQuery); + typeProperties.Add("QuickInfoType", quickInfoType); + typeList.Add(typeProperties); + } + + } + } + } + return typeList; + } + + #> \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinitionSupportedTypes.xml b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinitionSupportedTypes.xml new file mode 100644 index 00000000..b5325208 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/PeekDefinitionSupportedTypes.xml @@ -0,0 +1,66 @@ + + + + Table + Table + Table + table + true + + + View + View + View + view + true + + + StoredProcedure + Procedure + StoredProcedure + stored procedure + true + + + UserDefinedDataType + Type + UserDefinedDataType + user-defined data type + true + + + UserDefinedTableType + Type + UserDefinedTableType + user-defined table type + true + + + Synonym + Synonym + Synonym + + true + + + ScalarValuedFunction + Function + UserDefinedFunction + scalar-valued function + true + + + TableValuedFunction + Function + UserDefinedFunction + table-valued function + true + + diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/PeekDefinitionTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/PeekDefinitionTests.cs index d3113b86..e619e657 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/PeekDefinitionTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/PeekDefinitionTests.cs @@ -4,29 +4,23 @@ // using System; using System.IO; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using System.Runtime.InteropServices; using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.SqlParser.Binder; -using Microsoft.SqlServer.Management.SqlParser.Intellisense; -using Microsoft.SqlServer.Management.SqlParser.MetadataProvider; -using Microsoft.SqlServer.Management.SqlParser.Parser; using Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlServer.Management.SqlParser.MetadataProvider; using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; -using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts; using Microsoft.SqlTools.ServiceLayer.LanguageServices; -using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; +using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion; using Microsoft.SqlTools.ServiceLayer.SqlContext; -using Microsoft.SqlTools.ServiceLayer.QueryExecution; -using Microsoft.SqlTools.ServiceLayer.Test.QueryExecution; using Microsoft.SqlTools.ServiceLayer.Workspace; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using Microsoft.SqlTools.Test.Utility; using Moq; using Xunit; using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location; +using Microsoft.SqlServer.Management.SqlParser.Intellisense; namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServices { @@ -114,9 +108,10 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServices { Line = 0, // test for 'dbo' - Character = 16 + Character = 15 } }; + TestConnectionResult connectionResult = await TestObjects.InitLiveConnectionInfo(); connectionResult.ScriptFile.Contents = "select * from dbo.func ()"; var languageService = new LanguageService(); @@ -142,15 +137,20 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServices PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); string objectName = "from"; - - List declarations = new List(); - DefinitionResult result = peekDefinition.GetScript(declarations, objectName, null); + Position position = new Position() + { + Line = 1, + Character = 14 + }; + ScriptParseInfo scriptParseInfo = new ScriptParseInfo() { IsConnected = true }; + Mock bindingContextMock = new Mock(); + DefinitionResult result = peekDefinition.GetScript(scriptParseInfo.ParseResult, position, bindingContextMock.Object.MetadataDisplayInfoProvider, objectName, null); Assert.NotNull(result); Assert.True(result.IsErrorResult); Assert.Equal(SR.PeekDefinitionNoResultsError, result.Message); } - + /// /// Test GetDefinition with a forced timeout. Expect a error result. /// @@ -164,9 +164,9 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServices ManualResetEvent mre = new ManualResetEvent(true); // Do not block Mock itemMock = new Mock(); itemMock.Setup(i => i.ItemProcessed).Returns(mre); - + DefinitionResult timeoutResult = null; - + queueMock.Setup(q => q.QueueBindingOperation( It.IsAny(), It.IsAny>(), @@ -174,7 +174,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServices It.IsAny(), It.IsAny())) .Callback, Func, int?, int?>( - (key, bindOperation, timeoutOperation, blah, blah2) => + (key, bindOperation, timeoutOperation, t1, t2) => { timeoutResult = (DefinitionResult) timeoutOperation((IBindingContext)null); itemMock.Object.Result = timeoutResult; @@ -306,6 +306,387 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServices Cleanup(locations); } + /// + /// Test get definition for a scalar valued function object with active connection and explicit schema name. Expect non-null locations + /// + [Fact] + public async Task GetScalarValuedFunctionDefinitionWithSchemaNameSuccessTest() + { + // Get live connectionInfo and serverConnection + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "pd_addTwo"; + string schemaName = "dbo"; + string objectType = "FUNCTION"; + + Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetScalarValuedFunctionScripts, objectName, schemaName, objectType); + Assert.NotNull(locations); + Cleanup(locations); + } + + /// + /// Test get definition for a table valued function object with active connection and explicit schema name. Expect non-null locations + /// + [Fact] + public async Task GetTableValuedFunctionDefinitionWithSchemaNameSuccessTest() + { + // Get live connectionInfo and serverConnection + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "pd_returnTable"; + string schemaName = "dbo"; + string objectType = "FUNCTION"; + + Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetTableValuedFunctionScripts, objectName, schemaName, objectType); + Assert.NotNull(locations); + Cleanup(locations); + } + + /// + /// Test get definition for a scalar valued function object that doesn't exist with active connection. Expect null locations + /// + [Fact] + public async Task GetScalarValuedFunctionDefinitionWithNonExistentFailureTest() + { + // Get live connectionInfo and serverConnection + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "doesNotExist"; + string schemaName = "dbo"; + string objectType = "FUNCTION"; + + Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetScalarValuedFunctionScripts, objectName, schemaName, objectType); + Assert.Null(locations); + } + + /// + /// Test get definition for a table valued function object that doesn't exist with active connection. Expect null locations + /// + [Fact] + public async Task GetTableValuedFunctionDefinitionWithNonExistentObjectFailureTest() + { + // Get live connectionInfo and serverConnection + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "doesNotExist"; + string schemaName = "dbo"; + string objectType = "FUNCTION"; + + Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetTableValuedFunctionScripts, objectName, schemaName, objectType); + Assert.Null(locations); + } + + /// + /// Test get definition for a scalar valued function object with active connection. Expect non-null locations + /// + [Fact] + public async Task GetScalarValuedFunctionDefinitionWithoutSchemaNameSuccessTest() + { + // Get live connectionInfo and serverConnection + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "pd_addTwo"; + string schemaName = null; + string objectType = "FUNCTION"; + + Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetScalarValuedFunctionScripts, objectName, schemaName, objectType); + Assert.NotNull(locations); + Cleanup(locations); + } + + /// + /// Test get definition for a table valued function object with active connection. Expect non-null locations + /// + [Fact] + public async Task GetTableValuedFunctionDefinitionWithoutSchemaNameSuccessTest() + { + // Get live connectionInfo and serverConnection + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "pd_returnTable"; + string schemaName = null; + string objectType = "FUNCTION"; + + Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetTableValuedFunctionScripts, objectName, schemaName, objectType); + Assert.NotNull(locations); + Cleanup(locations); + } + + + /// + /// Test get definition for a user defined data type object with active connection and explicit schema name. Expect non-null locations + /// + [Fact] + public async Task GetUserDefinedDataTypeDefinitionWithSchemaNameSuccessTest() + { + // Get live connectionInfo and serverConnection + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "pd_ssn"; + string schemaName = "dbo"; + string objectType = "Type"; + + Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetUserDefinedDataTypeScripts, objectName, schemaName, objectType); + Assert.NotNull(locations); + Cleanup(locations); + } + + /// + /// Test get definition for a user defined data type object with active connection. Expect non-null locations + /// + [Fact] + public async Task GetUserDefinedDataTypeDefinitionWithoutSchemaNameSuccessTest() + { + // Get live connectionInfo and serverConnection + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "pd_ssn"; + string schemaName = null; + string objectType = "Type"; + + Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetUserDefinedDataTypeScripts, objectName, schemaName, objectType); + Assert.NotNull(locations); + Cleanup(locations); + } + + /// + /// Test get definition for a user defined data type object that doesn't exist with active connection. Expect null locations + /// + [Fact] + public async Task GetUserDefinedDataTypeDefinitionWithNonExistentFailureTest() + { + // Get live connectionInfo and serverConnection + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "doesNotExist"; + string schemaName = "dbo"; + string objectType = "Type"; + + Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetUserDefinedDataTypeScripts, objectName, schemaName, objectType); + Assert.Null(locations); + } + + /// + /// Test get definition for a user defined table type object with active connection and explicit schema name. Expect non-null locations + /// + [Fact] + public async Task GetUserDefinedTableTypeDefinitionWithSchemaNameSuccessTest() + { + // Get live connectionInfo and serverConnection + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "pd_locationTableType"; + string schemaName = "dbo"; + string objectType = "Type"; + + Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetUserDefinedTableTypeScripts, objectName, schemaName, objectType); + Assert.NotNull(locations); + Cleanup(locations); + } + + /// + /// Test get definition for a user defined table type object with active connection. Expect non-null locations + /// + [Fact] + public async Task GetUserDefinedTableTypeDefinitionWithoutSchemaNameSuccessTest() + { + // Get live connectionInfo and serverConnection + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "pd_locationTableType"; + string schemaName = null; + string objectType = "Type"; + + Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetUserDefinedTableTypeScripts, objectName, schemaName, objectType); + Assert.NotNull(locations); + Cleanup(locations); + } + + /// + /// Test get definition for a user defined table type object that doesn't exist with active connection. Expect null locations + /// + [Fact] + public async Task GetUserDefinedTableTypeDefinitionWithNonExistentFailureTest() + { + // Get live connectionInfo and serverConnection + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "doesNotExist"; + string schemaName = "dbo"; + string objectType = "Type"; + + Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetUserDefinedTableTypeScripts, objectName, schemaName, objectType); + Assert.Null(locations); + } + + /// + /// Test get definition for a synonym object with active connection and explicit schema name. Expect non-null locations + /// + [Fact] + public async Task GetSynonymDefinitionWithSchemaNameSuccessTest() + { + // Get live connectionInfo and serverConnection + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "pd_testTable"; + string schemaName = "dbo"; + string objectType = "Synonym"; + + Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetSynonymScripts, objectName, schemaName, objectType); + Assert.NotNull(locations); + Cleanup(locations); + } + + + /// + /// Test get definition for a Synonym object with active connection. Expect non-null locations + /// + [Fact] + public async Task GetSynonymDefinitionWithoutSchemaNameSuccessTest() + { + // Get live connectionInfo and serverConnection + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "pd_testTable"; + string schemaName = null; + string objectType = "Synonym"; + + Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetSynonymScripts, objectName, schemaName, objectType); + Assert.NotNull(locations); + Cleanup(locations); + } + + /// + /// Test get definition for a Synonym object that doesn't exist with active connection. Expect null locations + /// + [Fact] + public async Task GetSynonymDefinitionWithNonExistentFailureTest() + { + // Get live connectionInfo and serverConnection + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "doesNotExist"; + string schemaName = "dbo"; + string objectType = "Synonym"; + + Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetSynonymScripts, objectName, schemaName, objectType); + Assert.Null(locations); + } + + /// + /// Test get definition using declaration type for a view object with active connection + /// Expect a non-null result with location + /// + [Fact] + public async Task GetDefinitionUsingDeclarationTypeWithValidObjectTest() + { + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "objects"; + string schemaName = "sys"; + + DefinitionResult result = peekDefinition.GetDefinitionUsingDeclarationType(DeclarationType.View, "master.sys.objects", objectName, schemaName); + Assert.NotNull(result); + Assert.NotNull(result.Locations); + Assert.False(result.IsErrorResult); + Cleanup(result.Locations); + + } + + /// + /// Test get definition using declaration type for a non existent view object with active connection + /// Expect a non-null result with location + /// + [Fact] + public async Task GetDefinitionUsingDeclarationTypeWithNonexistentObjectTest() + { + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "doesNotExist"; + string schemaName = "sys"; + + DefinitionResult result = peekDefinition.GetDefinitionUsingDeclarationType(DeclarationType.View, "master.sys.objects", objectName, schemaName); + Assert.NotNull(result); + Assert.True(result.IsErrorResult); + } + + /// + /// Test get definition using quickInfo text for a view object with active connection + /// Expect a non-null result with location + /// + [Fact] + public async Task GetDefinitionUsingQuickInfoTextWithValidObjectTest() + { + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "objects"; + string schemaName = "sys"; + string quickInfoText = "view master.sys.objects"; + + DefinitionResult result = peekDefinition.GetDefinitionUsingQuickInfoText(quickInfoText, objectName, schemaName); + Assert.NotNull(result); + Assert.NotNull(result.Locations); + Assert.False(result.IsErrorResult); + Cleanup(result.Locations); + + } + + /// + /// Test get definition using quickInfo text for a view object with active connection + /// Expect a non-null result with location + /// + [Fact] + public async Task GetDefinitionUsingQuickInfoTextWithNonexistentObjectTest() + { + ConnectionInfo connInfo = await TestObjects.InitLiveConnectionInfoForDefinition(); + ServerConnection serverConnection = TestObjects.InitLiveServerConnectionForDefinition(connInfo); + + PeekDefinition peekDefinition = new PeekDefinition(serverConnection, connInfo); + string objectName = "doesNotExist"; + string schemaName = "sys"; + string quickInfoText = "view master.sys.objects"; + + DefinitionResult result = peekDefinition.GetDefinitionUsingQuickInfoText(quickInfoText, objectName, schemaName); + Assert.NotNull(result); + Assert.True(result.IsErrorResult); + } + /// /// Helper method to clean up script files /// diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs index cc1d3e8e..a1d19476 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs @@ -220,7 +220,155 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices FileUtils.SafeDirectoryDelete(FileUtils.PeekDefinitionTempFolder, true); Assert.False(Directory.Exists(FileUtils.PeekDefinitionTempFolder)); // Expected not to throw any exception - languageService.DeletePeekDefinitionScripts(); + languageService.DeletePeekDefinitionScripts(); + } + + /// + /// Test Extracting the full object name from quickInfoText. + /// Given a valid object name string and a vaild quickInfo string containing the object name + /// Expect the full object name (database.schema.objectName) + /// + [Fact] + public void GetFullObjectNameFromQuickInfoWithValidStringsTest() + { + PeekDefinition peekDefinition = new PeekDefinition(null, null); + string objectName = "testTable"; + string quickInfoText = "table master.dbo.testTable"; + string result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName); + string expected = "master.dbo.testTable"; + Assert.Equal(expected, result); + } + + /// + /// Test Extracting the full object name from quickInfoText. + /// Given a null object name string and a vaild quickInfo string containing the object name( and vice versa) + /// Expect null + /// + [Fact] + public void GetFullObjectNameFromQuickInfoWithNullStringsTest() + { + PeekDefinition peekDefinition = new PeekDefinition(null, null); + string expected = null; + + string objectName = null; + string quickInfoText = "table master.dbo.testTable"; + string result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName); + Assert.Equal(expected, result); + + quickInfoText = null; + objectName = "tableName"; + result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName); + Assert.Equal(expected, result); + + quickInfoText = null; + objectName = null; + result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName); + Assert.Equal(expected, result); + } + + /// + /// Test Extracting the full object name from quickInfoText. + /// Given a valid object name string and a vaild quickInfo string that does not contain the object name + /// Expect null + /// + [Fact] + public void GetFullObjectNameFromQuickInfoWithIncorrectObjectNameTest() + { + PeekDefinition peekDefinition = new PeekDefinition(null, null); + string objectName = "test"; + string quickInfoText = "table master.dbo.tableName"; + string result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName); + string expected = null; + Assert.Equal(expected, result); + } + + /// + /// Test Extracting the object type from quickInfoText. + /// Given a valid object name string and a vaild quickInfo string containing the object name + /// Expect correct object type + /// + [Fact] + public void GetTokenTypeFromQuickInfoWithValidStringsTest() + { + PeekDefinition peekDefinition = new PeekDefinition(null, null); + string objectName = "tableName"; + string quickInfoText = "table master.dbo.tableName"; + string result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName); + string expected = "table"; + Assert.Equal(expected, result); + } + + /// + /// Test Extracting theobject type from quickInfoText. + /// Given a null object name string and a vaild quickInfo string containing the object name( and vice versa) + /// Expect null + /// + [Fact] + public void GetTokenTypeFromQuickInfoWithNullStringsTest() + { + PeekDefinition peekDefinition = new PeekDefinition(null, null); + string expected = null; + + string objectName = null; + string quickInfoText = "table master.dbo.testTable"; + string result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName); + Assert.Equal(expected, result); + + quickInfoText = null; + objectName = "tableName"; + result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName); + Assert.Equal(expected, result); + + quickInfoText = null; + objectName = null; + result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName); + Assert.Equal(expected, result); + } + + /// + /// Test Extracting the object type from quickInfoText. + /// Given a valid object name string and a vaild quickInfo string that does not containthe object name + /// Expect null + /// + [Fact] + public void GetTokenTypeFromQuickInfoWithIncorrectObjectNameTest() + { + PeekDefinition peekDefinition = new PeekDefinition(null, null); + string objectName = "test"; + string quickInfoText = "table master.dbo.tableName"; + string result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName); + string expected = null; + Assert.Equal(expected, result); + } + + /// + /// test Getting definition using quickInfo text without a live connection + /// Expect an error result( because you cannot script without a live connection) + /// + [Fact] + public void GetDefinitionUsingQuickInfoWithoutConnectionTest() + { + PeekDefinition peekDefinition = new PeekDefinition(null, null); + string objectName = "tableName"; + string quickInfoText = "table master.dbo.tableName"; + DefinitionResult result = peekDefinition.GetDefinitionUsingQuickInfoText(quickInfoText, objectName, null); + Assert.NotNull(result); + Assert.True(result.IsErrorResult); + } + + /// + /// test Getting definition using declarration Type without a live connection + /// Expect an error result( because you cannot script without a live connection) + /// + [Fact] + public void GetDefinitionUsingDeclarationItemWithoutConnectionTest() + { + PeekDefinition peekDefinition = new PeekDefinition(null, null); + string objectName = "tableName"; + string fullObjectName = "master.dbo.tableName"; + DefinitionResult result = peekDefinition.GetDefinitionUsingDeclarationType(DeclarationType.Table, fullObjectName, objectName, null); + Assert.NotNull(result); + Assert.True(result.IsErrorResult); } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.TestDriver.Tests/LanguageServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.TestDriver.Tests/LanguageServiceTests.cs index 007fc3e6..f7803176 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.TestDriver.Tests/LanguageServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.TestDriver.Tests/LanguageServiceTests.cs @@ -45,7 +45,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Tests }; await testService.RequestOpenDocumentNotification(openParams); - + Thread.Sleep(500); bool connected = await testService.Connect(TestServerType.OnPrem, queryTempFile.FilePath); @@ -86,7 +86,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Tests }; await testService.RequestOpenDocumentNotification(openParams); - + Thread.Sleep(500); bool connected = await testService.Connect(TestServerType.OnPrem, queryTempFile.FilePath); @@ -136,7 +136,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Tests }; await testService.RequestOpenDocumentNotification(openParams); - + Thread.Sleep(100); var contentChanges = new TextDocumentChangeEvent[1]; @@ -172,7 +172,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Tests await testService.RequestChangeTextDocumentNotification(changeParams); Thread.Sleep(100); - + contentChanges[0] = new TextDocumentChangeEvent { Range = new Range @@ -211,7 +211,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Tests } /// - /// Peek Definition/ Go to definition + /// Peek Definition/ Go to definition /// /// [Fact] @@ -238,16 +238,20 @@ namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Tests }; await testService.RequestOpenDocumentNotification(openParams); - + Thread.Sleep(500); bool connected = await testService.Connect(TestServerType.OnPrem, queryTempFile.FilePath); + + // Wait for intellisense to be ready + var readyParams = await testService.Driver.WaitForEvent(IntelliSenseReadyNotification.Type, 30000); + Assert.NotNull(readyParams); Assert.True(connected, "Connection is successful"); - Thread.Sleep(10000); + // Request definition for "objects" Location[] locations = await testService.RequestDefinition(queryTempFile.FilePath, query, lineNumber, position); - + Assert.True(locations != null, "Location is not null and not empty"); await testService.Disconnect(queryTempFile.FilePath); } @@ -265,7 +269,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Tests bool connected = await testService.Connect(TestServerType.OnPrem, queryTempFile.FilePath); Assert.True(connected, "Connection was not successful"); - Thread.Sleep(500); + Thread.Sleep(500); var settings = new SqlToolsSettings(); settings.SqlTools.IntelliSense.EnableIntellisense = false; @@ -422,10 +426,10 @@ namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Tests public async Task VerifyFunctionSignatureHelpParameter( TestServiceDriverProvider TestService, - string ownerUri, - int character, - string expectedFunctionName, - int expectedParameterIndex, + string ownerUri, + int character, + string expectedFunctionName, + int expectedParameterIndex, string expectedParameterName) { var position = new TextDocumentPosition()