diff --git a/.travis.yml b/.travis.yml index d98d3945..3a04a0a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,5 +39,6 @@ script: - dotnet test test/Microsoft.SqlTools.ServiceLayer.Test env: - - ProjectPath=home/travis/build/Microsoft/sqltoolsservice +# Since we are building from root, current directory is the project path + - ProjectPath=./ \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs index 2ca48d8c..a47f8460 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs @@ -720,7 +720,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection } // open connection based on request details - result = await Instance.Connect(connectParams); + result = await Connect(connectParams); await ServiceHost.SendEvent(ConnectionCompleteNotification.Type, result); } catch (Exception ex) @@ -745,7 +745,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection try { - bool result = Instance.CancelConnect(cancelParams); + bool result = CancelConnect(cancelParams); await requestContext.SendResult(result); } catch(Exception ex) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/ReliableConnectionHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/ReliableConnectionHelper.cs index 2cad15d4..4a5a559e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/ReliableConnectionHelper.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ReliableConnection/ReliableConnectionHelper.cs @@ -55,7 +55,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection try { SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectionString); - Debug.Assert(builder.Pooling == false, "Pooling should be false"); + Debug.Assert(!builder.Pooling, "Pooling should be false"); } catch (Exception ex) { @@ -63,7 +63,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection } #endif - if (AmbientSettings.AlwaysRetryOnTransientFailure == true) + if (AmbientSettings.AlwaysRetryOnTransientFailure) { useRetry = true; } @@ -368,8 +368,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection IDbCommand cmd = null; try { - Debug.Assert(conn.State == ConnectionState.Open, "connection passed to ExecuteReader should be open."); - cmd = conn.CreateCommand(); if (initializeCommand == null) @@ -603,11 +601,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection Validate.IsNotNullOrEmptyString(nameof(commandText), commandText); string filePath = ExecuteScalar(conn, commandText, initializeCommand, catchException) as string; - if (!String.IsNullOrWhiteSpace(filePath)) + if (!string.IsNullOrWhiteSpace(filePath)) { // Remove filename from the filePath Uri pathUri; - if (Uri.TryCreate(filePath, UriKind.Absolute, out pathUri) == false) + if (!Uri.TryCreate(filePath, UriKind.Absolute, out pathUri)) { // Invalid Uri return null; @@ -931,7 +929,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection serverInfo = GetServerVersion(connection); - tempDatabaseName = (String.IsNullOrEmpty(builder.InitialCatalog) == false) ? + tempDatabaseName = (!string.IsNullOrEmpty(builder.InitialCatalog)) ? builder.InitialCatalog : builder.AttachDBFilename; // If at this point the dbName remained an empty string then diff --git a/src/Microsoft.SqlTools.ServiceLayer/Credentials/CredentialService.cs b/src/Microsoft.SqlTools.ServiceLayer/Credentials/CredentialService.cs index 6b558f8c..0054ec05 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Credentials/CredentialService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Credentials/CredentialService.cs @@ -58,7 +58,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Credentials /// internal CredentialService(ICredentialStore store, StoreConfig config) { - this.credStore = store != null ? store : GetStoreForOS(config); + credStore = store != null ? store : GetStoreForOS(config); } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/Extensibility/ExtensionServiceProvider.cs b/src/Microsoft.SqlTools.ServiceLayer/Extensibility/ExtensionServiceProvider.cs new file mode 100644 index 00000000..a3802e36 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Extensibility/ExtensionServiceProvider.cs @@ -0,0 +1,184 @@ +// +// 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.Collections; +using System.Collections.Generic; +using System.Composition.Convention; +using System.Composition.Hosting; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using Microsoft.Extensions.DependencyModel; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Extensibility +{ + public class ExtensionServiceProvider : RegisteredServiceProvider + { + private Func config; + + public ExtensionServiceProvider(Func config) + { + Validate.IsNotNull(nameof(config), config); + this.config = config; + } + + public static ExtensionServiceProvider CreateDefaultServiceProvider() + { + return Create(typeof(ExtensionStore).GetTypeInfo().Assembly.SingleItemAsEnumerable()); + } + + public static ExtensionServiceProvider Create(IEnumerable assemblies) + { + Validate.IsNotNull(nameof(assemblies), assemblies); + return new ExtensionServiceProvider(conventions => new ContainerConfiguration().WithAssemblies(assemblies, conventions)); + } + + public static ExtensionServiceProvider Create(IEnumerable types) + { + Validate.IsNotNull(nameof(types), types); + return new ExtensionServiceProvider(conventions => new ContainerConfiguration().WithParts(types, conventions)); + } + + protected override IEnumerable GetServicesImpl() + { + EnsureExtensionStoreRegistered(); + return base.GetServicesImpl(); + } + + private void EnsureExtensionStoreRegistered() + { + if (!services.ContainsKey(typeof(T))) + { + ExtensionStore store = new ExtensionStore(typeof(T), config); + base.Register(() => store.GetExports()); + } + } + } + + /// + /// A store for MEF exports of a specific type. Provides basic wrapper functionality around MEF to standarize how + /// we lookup types and return to callers. + /// + public class ExtensionStore + { + private CompositionHost host; + private IList exports; + private Type contractType; + + /// + /// Initializes the store with a type to lookup exports of, and a function that configures the + /// lookup parameters. + /// + /// Type to use as a base for all extensions being looked up + /// Function that returns the configuration to be used + public ExtensionStore(Type contractType, Func configure) + { + Validate.IsNotNull(nameof(contractType), contractType); + Validate.IsNotNull(nameof(configure), configure); + this.contractType = contractType; + ConventionBuilder builder = GetExportBuilder(); + ContainerConfiguration config = configure(builder); + host = config.CreateContainer(); + } + + /// + /// Loads extensions from the current assembly + /// + /// ExtensionStore + public static ExtensionStore CreateDefaultLoader() + { + return CreateAssemblyStore(typeof(ExtensionStore).GetTypeInfo().Assembly); + } + + public static ExtensionStore CreateAssemblyStore(Assembly assembly) + { + Validate.IsNotNull(nameof(assembly), assembly); + return new ExtensionStore(typeof(T), (conventions) => + new ContainerConfiguration().WithAssembly(assembly, conventions)); + } + + public static ExtensionStore CreateStoreForCurrentDirectory() + { + string assemblyPath = typeof(ExtensionStore).GetTypeInfo().Assembly.Location; + string directory = Path.GetDirectoryName(assemblyPath); + return new ExtensionStore(typeof(T), (conventions) => + new ContainerConfiguration().WithAssembliesInPath(directory, conventions)); + } + + public IEnumerable GetExports() + { + if (exports == null) + { + exports = host.GetExports(contractType).ToList(); + } + return exports.Cast(); + } + + private ConventionBuilder GetExportBuilder() + { + // Define exports as matching a parent type, export as that parent type + var builder = new ConventionBuilder(); + builder.ForTypesDerivedFrom(contractType).Export(exportConventionBuilder => exportConventionBuilder.AsContractType(contractType)); + return builder; + } + } + + public static class ContainerConfigurationExtensions + { + public static ContainerConfiguration WithAssembliesInPath(this ContainerConfiguration configuration, string path, SearchOption searchOption = SearchOption.TopDirectoryOnly) + { + return WithAssembliesInPath(configuration, path, null, searchOption); + } + + public static ContainerConfiguration WithAssembliesInPath(this ContainerConfiguration configuration, string path, AttributedModelProvider conventions, SearchOption searchOption = SearchOption.TopDirectoryOnly) + { + AssemblyLoadContext context = new AssemblyLoader(path); + var assemblyNames = Directory + .GetFiles(path, "*.dll", searchOption) + .Select(AssemblyLoadContext.GetAssemblyName); + + var assemblies = assemblyNames + .Select(context.LoadFromAssemblyName) + .ToList(); + + configuration = configuration.WithAssemblies(assemblies, conventions); + + return configuration; + } + } + + public class AssemblyLoader : AssemblyLoadContext + { + private string folderPath; + + public AssemblyLoader(string folderPath) + { + this.folderPath = folderPath; + } + + protected override Assembly Load(AssemblyName assemblyName) + { + var deps = DependencyContext.Default; + var res = deps.CompileLibraries.Where(d => d.Name.Equals(assemblyName.Name)).ToList(); + if (res.Count > 0) + { + return Assembly.Load(new AssemblyName(res.First().Name)); + } + else + { + var apiApplicationFileInfo = new FileInfo($"{folderPath}{Path.DirectorySeparatorChar}{assemblyName.Name}.dll"); + if (File.Exists(apiApplicationFileInfo.FullName)) + { + var asl = new AssemblyLoader(apiApplicationFileInfo.DirectoryName); + return asl.LoadFromAssemblyPath(apiApplicationFileInfo.FullName); + } + } + return Assembly.Load(assemblyName); + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Extensibility/IComposableService.cs b/src/Microsoft.SqlTools.ServiceLayer/Extensibility/IComposableService.cs new file mode 100644 index 00000000..072b12e5 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Extensibility/IComposableService.cs @@ -0,0 +1,22 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + + +namespace Microsoft.SqlTools.ServiceLayer.Extensibility +{ + /// + /// A Service that expects to lookup other services. Using this interface on an exported service + /// will ensure the method is called during + /// service initialization + /// + public interface IComposableService + { + /// + /// Supports settings the service provider being used to initialize the service. + /// This is useful to look up other services and use them in your own service. + /// + void SetServiceProvider(IMultiServiceProvider provider); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Extensibility/IEnumerableExt.cs b/src/Microsoft.SqlTools.ServiceLayer/Extensibility/IEnumerableExt.cs new file mode 100644 index 00000000..07412622 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Extensibility/IEnumerableExt.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Collections.Generic; + +namespace Microsoft.SqlTools.ServiceLayer.Extensibility +{ + + internal static class IEnumerableExt + { + public static IEnumerable SingleItemAsEnumerable(this T item) + { + yield return item; + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Extensibility/IMultiServiceProvider.cs b/src/Microsoft.SqlTools.ServiceLayer/Extensibility/IMultiServiceProvider.cs new file mode 100644 index 00000000..5a11065d --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Extensibility/IMultiServiceProvider.cs @@ -0,0 +1,104 @@ +// +// 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.Collections.Generic; +using System.Linq; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Extensibility +{ + public interface IMultiServiceProvider + { + /// + /// Gets a service of a specific type. It is expected that only 1 instance of this type will be + /// available + /// + /// Type of service to be found + /// Instance of T or null if not found + /// The input sequence contains more than one element.-or-The input sequence is empty. + T GetService(); + + /// + /// Gets a service of a specific type. The first service matching the specified filter will be returned + /// available + /// + /// Type of service to be found + /// Filter to use in + /// Instance of T or null if not found + /// The input sequence contains more than one element.-or-The input sequence is empty. + T GetService(Predicate filter); + + /// + /// Gets multiple services of a given type + /// + /// + /// An enumerable of matching services + IEnumerable GetServices(); + + /// + /// Gets multiple services of a given type, where they match a filter + /// + /// + /// + /// + IEnumerable GetServices(Predicate filter); + } + + + public abstract class ServiceProviderBase : IMultiServiceProvider + { + + public T GetService() + { + return GetServices().SingleOrDefault(); + } + + public T GetService(Predicate filter) + { + Validate.IsNotNull(nameof(filter), filter); + return GetServices().Where(t => filter(t)).SingleOrDefault(); + } + + public IEnumerable GetServices(Predicate filter) + { + Validate.IsNotNull(nameof(filter), filter); + return GetServices().Where(t => filter(t)); + } + + public virtual IEnumerable GetServices() + { + var services = GetServicesImpl(); + if (services == null) + { + return Enumerable.Empty(); + } + + return services.Select(t => + { + InitComposableService(t); + return t; + }); + } + + private void InitComposableService(T t) + { + IComposableService c = t as IComposableService; + if (c != null) + { + c.SetServiceProvider(this); + } + } + + /// + /// Gets all services using the build in implementation + /// + /// + /// + protected abstract IEnumerable GetServicesImpl(); + + } + +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Extensibility/RegisteredServiceProvider.cs b/src/Microsoft.SqlTools.ServiceLayer/Extensibility/RegisteredServiceProvider.cs new file mode 100644 index 00000000..fe205eda --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Extensibility/RegisteredServiceProvider.cs @@ -0,0 +1,110 @@ +// +// 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.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Extensibility +{ + + /// + /// A service provider implementation that allows registering of specific services + /// + public class RegisteredServiceProvider : ServiceProviderBase + { + public delegate IEnumerable ServiceLookup(); + + protected Dictionary services = new Dictionary(); + + /// + /// Registers a singular service to be returned during lookup + /// + /// + /// this provider, to simplify fluent declarations + /// If service is null + /// If an existing service is already registered + public RegisteredServiceProvider RegisterSingleService(T service) + { + Validate.IsNotNull(nameof(service), service); + ThrowIfAlreadyRegistered(); + services.Add(typeof(T), () => service.SingleItemAsEnumerable()); + return this; + } + + /// + /// Registers a singular service to be returned during lookup + /// + /// + /// Type or interface this service should be registed as. Any request + /// for that type will return this service + /// + /// service object to be added + /// this provider, to simplify fluent declarations + /// If service is null + /// If an existing service is already registered + public RegisteredServiceProvider RegisterSingleService(Type type, object service) + { + Validate.IsNotNull(nameof(type), type); + Validate.IsNotNull(nameof(service), service); + ThrowIfAlreadyRegistered(type); + ThrowIfIncompatible(type, service); + services.Add(type, () => service.SingleItemAsEnumerable()); + return this; + } + + /// + /// Registers a function that can look up multiple services + /// + /// + /// this provider, to simplify fluent declarations + /// If is null + /// If an existing service is already registered + public RegisteredServiceProvider Register(Func> serviceLookup) + { + Validate.IsNotNull(nameof(serviceLookup), serviceLookup); + ThrowIfAlreadyRegistered(); + services.Add(typeof(T), () => serviceLookup()); + return this; + } + + private void ThrowIfAlreadyRegistered() + { + ThrowIfAlreadyRegistered(typeof(T)); + } + + private void ThrowIfAlreadyRegistered(Type type) + { + if (services.ContainsKey(type)) + { + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.ServiceAlreadyRegistered, type.Name)); + } + + } + + private void ThrowIfIncompatible(Type type, object service) + { + if (!type.IsInstanceOfType(service)) + { + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.ServiceNotOfExpectedType, service.GetType().Name, type.Name)); + } + + } + + protected override IEnumerable GetServicesImpl() + { + ServiceLookup serviceLookup; + if (services.TryGetValue(typeof(T), out serviceLookup)) + { + return serviceLookup().Cast(); + } + return Enumerable.Empty(); + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Contracts/DocumentFormatting.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Contracts/DocumentFormatting.cs new file mode 100644 index 00000000..1d2778a4 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Contracts/DocumentFormatting.cs @@ -0,0 +1,113 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts; +using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; +using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter.Contracts +{ + /// + /// A formatting request to process an entire document + /// + public class DocumentFormattingRequest + { + public static readonly + RequestType Type = + RequestType.Create("textDocument/formatting"); + } + + /// + /// A formatting request to process a specific range inside a document + /// + public class DocumentRangeFormattingRequest + { + public static readonly + RequestType Type = + RequestType.Create("textDocument/rangeFormatting"); + } + + /// + /// A formatting request to handle a user typing, giving a chance to update the text based on this + /// + public class DocumentOnTypeFormattingRequest + { + public static readonly + RequestType Type = + RequestType.Create("textDocument/onTypeFormatting"); + } + + + /// + /// Params for the + /// + public class DocumentFormattingParams + { + + /// + /// The document to format. + /// + public TextDocumentIdentifier TextDocument { get; set; } + + /// + /// The formatting options + /// + public FormattingOptions Options { get; set; } + + } + + + /// + /// Params for the + /// + public class DocumentRangeFormattingParams : DocumentFormattingParams + { + + /// + /// The range to format + /// + public Range Range { get; set; } + + } + + /// + /// Params for the + /// + public class DocumentOnTypeFormattingParams : DocumentFormattingParams + { + /// + /// The position at which this request was sent. + + /// + Position Position { get; set; } + + /// + /// The character that has been typed. + + /// + string Ch { get; set; } + } + + /// + /// Value-object describing what options formatting should use. + /// + public class FormattingOptions + { + /// + /// Size of a tab in spaces + /// + public int TabSize { get; set; } + + /// + /// Prefer spaces over tabs. + /// + public bool InsertSpaces { get; set; } + + // TODO there may be other options passed by VSCode - format is + // [key: string]: boolean | number | string; + // Determine how these might be passed and add them here +} + +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/ASTNodeFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/ASTNodeFormatter.cs new file mode 100644 index 00000000..651df2dc --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/ASTNodeFormatter.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Babel.ParserGenerator; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + public abstract class ASTNodeFormatter + { + /// + /// Formats the text for a specific node. + /// + public abstract void Format(); + + internal static LexLocation GetLexLocationForNode(SqlCodeObject obj) + { + return obj.Position; + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/ASTNodeFormatterT.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/ASTNodeFormatterT.cs new file mode 100644 index 00000000..ad00970d --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/ASTNodeFormatterT.cs @@ -0,0 +1,330 @@ +// +// 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.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Runtime.InteropServices; +using Babel.ParserGenerator; +using Microsoft.SqlServer.Management.SqlParser.Parser; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + internal abstract class ASTNodeFormatterT : ASTNodeFormatter where T : SqlCodeObject + { + protected FormatterVisitor Visitor { get; private set; } + protected T CodeObject { get; private set; } + + public ASTNodeFormatterT(FormatterVisitor visitor, T codeObject) + { + Validate.IsNotNull(nameof(visitor), visitor); + Validate.IsNotNull(nameof(codeObject), codeObject); + + Visitor = visitor; + CodeObject = codeObject; + } + + protected TokenManager TokenManager + { + get { return Visitor.Context.Script.TokenManager; } + } + + protected FormatOptions FormatOptions + { + get { return Visitor.Context.FormatOptions; } + } + + internal virtual void ProcessChild(SqlCodeObject child) + { + Validate.IsNotNull(nameof(child), child); + child.Accept(Visitor); + } + + protected void IncrementIndentLevel() + { + Visitor.Context.IncrementIndentLevel(); + } + + protected void DecrementIndentLevel() + { + Visitor.Context.DecrementIndentLevel(); + } + + protected void ProcessTokenRange(int startTokenNumber, int endTokenNumber) + { + Visitor.Context.ProcessTokenRange(startTokenNumber, endTokenNumber); + } + + protected void ProcessTokenRangeEnsuringOneNewLineMinumum(int startindex, int endIndex) + { + ProcessAndNormalizeWhitespaceRange(startindex, endIndex, FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum); + } + + protected void ProcessAndNormalizeWhitespaceRange(int startindex, int endIndex, NormalizeWhitespace normalizer) + { + ProcessAndNormalizeTokenRange(startindex, endIndex, normalizer, true); + } + + + protected void ProcessAndNormalizeTokenRange(int startindex, int endIndex, + NormalizeWhitespace normalizer, bool areAllTokensWhitespace) + { + for (int i = startindex; i < endIndex; i++) + { + ProcessTokenAndNormalize(i, normalizer, areAllTokensWhitespace); + } + } + + protected void ProcessTokenAndNormalize(int tokenIndex, NormalizeWhitespace normalizeFunction, bool areAllTokensWhitespace = true) + { + TokenData iTokenData = GetTokenData(tokenIndex); + + if (areAllTokensWhitespace) + { + DebugAssertTokenIsWhitespaceOrComment(iTokenData, tokenIndex); + } + normalizeFunction = normalizeFunction ?? FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum; + SimpleProcessToken(tokenIndex, normalizeFunction); + } + + protected void DebugAssertTokenIsWhitespaceOrComment(TokenData td, int tokenIndex) + { + Debug.Assert(TokenManager.IsTokenComment(td.TokenId) || IsTokenWhitespace(td), string.Format(CultureInfo.CurrentCulture, + "Unexpected token \"{0}\", expected whitespace or comment.", GetTextForCurrentToken(tokenIndex)) + ); + } + + /// + /// Logical aliases for ProcessTokenRange that indicates the starting region is to be analyzed + /// + internal virtual void ProcessPrefixRegion(int startTokenNumber, int firstChildStartTokenNumber) + { + ProcessTokenRange(startTokenNumber, firstChildStartTokenNumber); + } + + /// + /// Logical aliases for ProcessTokenRange that indicates the end region is to be analyzed + /// + internal virtual void ProcessSuffixRegion(int lastChildEndTokenNumber, int endTokenNumber) + { + ProcessTokenRange(lastChildEndTokenNumber, endTokenNumber); + } + + internal virtual void ProcessInterChildRegion(SqlCodeObject lastChild, SqlCodeObject nextChild) + { + Validate.IsNotNull(nameof(lastChild), lastChild); + Validate.IsNotNull(nameof(nextChild), nextChild); + + int lastChildEnd = lastChild.Position.endTokenNumber; + int nextChildStart = nextChild.Position.startTokenNumber; + + ProcessTokenRange(lastChildEnd, nextChildStart); + } + + public override void Format() + { + LexLocation loc = GetLexLocationForNode(CodeObject); + + SqlCodeObject firstChild = CodeObject.Children.FirstOrDefault(); + if (firstChild != null) + { + // + // format the text from the start of the object to the start of it's first child + // + LexLocation firstChildStart = GetLexLocationForNode(firstChild); + ProcessPrefixRegion(loc.startTokenNumber, firstChildStart.startTokenNumber); + + //LexLocation lastChildLexLocation = null; + SqlCodeObject previousChild = null; + foreach (SqlCodeObject child in CodeObject.Children) + { + // + // format text between the last child's end & current child's start + // + if (previousChild != null) + { + //ProcessInterChildRegion(lastChildLexLocation.endTokenNumber, childLexLocation.startTokenNumber); + ProcessInterChildRegion(previousChild, child); + } + + // + // format text of the the current child + // + ProcessChild(child); + previousChild = child; + + } + + // + // format text from end of last child to end of object. + // + Debug.Assert(previousChild != null, "last child is null. Need to write code to deal with this case"); + ProcessSuffixRegion(previousChild.Position.endTokenNumber, loc.endTokenNumber); + } + else + { + // no children + ProcessTokenRange(loc.startTokenNumber, loc.endTokenNumber); + } + } + + protected void SimpleProcessToken(int currentToken, NormalizeWhitespace normalizeFunction) + { + TokenData t = GetTokenData(currentToken); + if (IsTokenWhitespace(t)) + { + ProcessWhitepace(currentToken, normalizeFunction, t); + } + else if (t.TokenId == FormatterTokens.LEX_END_OF_LINE_COMMENT) + { + ProcessEndOfLine(currentToken, t); + } + else + { + ProcessTokenRange(currentToken, currentToken + 1); + } + } + + private void ProcessWhitepace(int currentToken, NormalizeWhitespace normalizeFunction, TokenData token) + { + string originalWhiteSpace = GetTextForCurrentToken(currentToken); + if (HasPreviousToken(currentToken)) + { + TokenData previousToken = PreviousTokenData(currentToken); + if (previousToken.TokenId == FormatterTokens.LEX_END_OF_LINE_COMMENT) + { + if (originalWhiteSpace.StartsWith("\n", StringComparison.OrdinalIgnoreCase) + && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Replace \n with \r\n on Windows platforms + originalWhiteSpace = Environment.NewLine + originalWhiteSpace.Substring(1); + } + } + } + + string newWhiteSpace = normalizeFunction(originalWhiteSpace, Visitor.Context); + + AddReplacement(new Replacement(token.StartIndex, GetTextForCurrentToken(currentToken), newWhiteSpace)); + } + + protected string GetTextForCurrentToken(int currentToken) + { + return Visitor.Context.GetTokenRangeAsOriginalString(currentToken, currentToken + 1); + } + + protected string GetTokenRangeAsOriginalString(int startTokenNumber, int endTokenNumber) + { + return Visitor.Context.GetTokenRangeAsOriginalString(startTokenNumber, endTokenNumber); + } + + private void ProcessEndOfLine(int currentToken, TokenData t) + { + // + // the new line character is split over the LEX_END_OF_LINE_COMMENT token and a following whitespace token. + // we deal with that here. + // + string comment = GetTextForCurrentToken(currentToken); + if (comment.EndsWith("\r", StringComparison.OrdinalIgnoreCase)) + { + AddReplacement(new Replacement(t.StartIndex, comment, comment.Substring(0, comment.Length - 1))); + } + } + + protected bool IsTokenWithIdWhitespace(int tokenId) + { + if (HasToken(tokenId)) + { + return TokenManager.IsTokenWhitespace(TokenManager.TokenList[tokenId].TokenId); + } + return false; + } + + protected bool IsTokenWhitespace(TokenData tokenData) + { + return TokenManager.IsTokenWhitespace(tokenData.TokenId); + } + + + protected TokenData GetTokenData(int currentToken) + { + if (HasToken(currentToken)) + { + return TokenManager.TokenList[currentToken]; + } + return default(TokenData); + } + + protected TokenData PreviousTokenData(int currentToken) + { + if (HasPreviousToken(currentToken)) + { + return TokenManager.TokenList[currentToken - 1]; + } + return default(TokenData); + } + + protected TokenData NextTokenData(int currentToken) + { + if (HasToken(currentToken)) + { + return TokenManager.TokenList[currentToken + 1]; + } + return default(TokenData); + } + + protected bool HasPreviousToken(int currentToken) + { + return HasToken(currentToken - 1); + } + + protected bool HasToken(int tokenIndex) + { + return tokenIndex >= 0 && tokenIndex < TokenManager.TokenList.Count; + } + + protected void AddReplacement(Replacement replacement) + { + Visitor.Context.Replacements.Add(replacement); + } + + protected void AddReplacement(int startIndex, string oldValue, string newValue) + { + AddReplacement(new Replacement(startIndex, oldValue, newValue)); + } + + protected void AddIndentedNewLineReplacement(int startIndex) + { + AddReplacement(new Replacement(startIndex, string.Empty, Environment.NewLine + Visitor.Context.GetIndentString())); + } + + protected string GetIndentString() + { + return Visitor.Context.GetIndentString(); + } + + /// + /// Finds an expected token + /// + /// Current index to start the search at + /// ID defining the type of token being looked for - e.g. parenthesis, INSERT + protected int FindTokenWithId(int currentIndex, int id) + { + TokenData td = GetTokenData(currentIndex); + while (td.TokenId != id && currentIndex < CodeObject.Position.endTokenNumber) + { + DebugAssertTokenIsWhitespaceOrComment(td, currentIndex); + ++currentIndex; + td = GetTokenData(currentIndex); + } + Debug.Assert(currentIndex < CodeObject.Position.endTokenNumber, "No token with ID" + id + " found in the columns definition."); + return currentIndex; + } + + internal delegate string NormalizeWhitespace(string original, FormatContext context); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/AstNodeFormatterFactory.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/AstNodeFormatterFactory.cs new file mode 100644 index 00000000..ec312280 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/AstNodeFormatterFactory.cs @@ -0,0 +1,39 @@ +// +// 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 Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + internal abstract class ASTNodeFormatterFactory + { + public abstract Type SupportedNodeType { get; } + public abstract ASTNodeFormatter Create(FormatterVisitor visitor, SqlCodeObject codeObject); + } + + internal abstract class ASTNodeFormatterFactoryT : ASTNodeFormatterFactory + where T : SqlCodeObject + { + public override Type SupportedNodeType + { + get + { + return typeof(T); + } + } + + public override ASTNodeFormatter Create(FormatterVisitor visitor, SqlCodeObject codeObject) + { + Validate.IsNotNull(nameof(visitor), visitor); + Validate.IsNotNull(nameof(codeObject), codeObject); + + return DoCreate(visitor, codeObject as T); + } + + protected abstract ASTNodeFormatter DoCreate(FormatterVisitor visitor, T codeObject); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/CommaSeparatedListFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/CommaSeparatedListFormatter.cs new file mode 100644 index 00000000..6af6e3db --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/CommaSeparatedListFormatter.cs @@ -0,0 +1,145 @@ +// +// 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.Diagnostics; +using Babel.ParserGenerator; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + internal class CommaSeparatedListFormatter : ASTNodeFormatterT + { + private bool PlaceEachElementOnNewLine { get; set; } + + internal CommaSeparatedListFormatter(FormatterVisitor visitor, SqlCodeObject codeObject, bool placeEachElementOnNewLine) + : base(visitor, codeObject) + { + PlaceEachElementOnNewLine = placeEachElementOnNewLine; + } + + internal override void ProcessPrefixRegion(int startTokenNumber, int firstChildStartTokenNumber) + { + IncrementIndentLevel(); + + NormalizeWhitespace f = FormatterUtilities.NormalizeNewLinesOrCondenseToOneSpace; + if (PlaceEachElementOnNewLine) + { + f = FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum; + } + + for (int i = startTokenNumber; i < firstChildStartTokenNumber; i++) + { + SimpleProcessToken(i, f); + } + } + + internal override void ProcessSuffixRegion(int lastChildEndTokenNumber, int endTokenNumber) + { + DecrementIndentLevel(); + ProcessTokenRange(lastChildEndTokenNumber, endTokenNumber); + } + + internal override void ProcessInterChildRegion(SqlCodeObject previousChild, SqlCodeObject nextChild) + { + Validate.IsNotNull(nameof(previousChild), previousChild); + Validate.IsNotNull(nameof(nextChild), nextChild); + + int start = previousChild.Position.endTokenNumber; + int end = nextChild.Position.startTokenNumber; + + bool foundNonWhitespaceTokenBeforeComma = false; + int commaToken = -1; + + for (int i = start; i < end && HasToken(i); i++) + { + TokenData td = GetTokenData(i); + if (td.TokenId == 44) + { + commaToken = i; + break; + } + else if (IsTokenWhitespace(td)) + { + foundNonWhitespaceTokenBeforeComma = true; + } + } + + Debug.Assert(commaToken > -1, "No comma separating the children."); + + if (foundNonWhitespaceTokenBeforeComma) + { + ProcessTokenRange(start, commaToken); + } + else + { + +#if DEBUG + for (int i = start; i < commaToken && HasToken(i); i++) + { + TokenData td = GetTokenData(i); + if (!IsTokenWhitespace(td)) + { + Debug.Fail("unexpected token type of " + td.TokenId); + } + } +#endif + + // strip whitespace before comma + for (int i = start; i < commaToken; i++) + { + SimpleProcessToken(i, FormatterUtilities.StripAllWhitespace); + } + } + + // include comma after each element? + if (!FormatOptions.PlaceCommasBeforeNextStatement) + { + ProcessTokenRange(commaToken, commaToken + 1); + } + else + { + TokenData token = GetTokenData(commaToken); + AddReplacement(new Replacement(token.StartIndex, ",", "")); + } + + // special case if there is no white space between comma token and end of region + if (commaToken + 1 == end) + { + string newValue = PlaceEachElementOnNewLine ? Environment.NewLine + GetIndentString() : " "; + AddReplacement(new Replacement( + GetTokenData(end).StartIndex, + string.Empty, + newValue + )); + } + else + { + NormalizeWhitespace f = FormatterUtilities.NormalizeNewLinesOrCondenseToOneSpace; + if (PlaceEachElementOnNewLine) + { + f = FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum; + } + + for (int i = commaToken + 1; i < end; i++) + { + SimpleProcessToken(i, f); + } + + } + + // do we need to place the comma before the next statement in the list? + if (FormatOptions.PlaceCommasBeforeNextStatement) + { + SimpleProcessToken(commaToken, FormatterUtilities.NormalizeNewLinesInWhitespace); + TokenData tok = GetTokenData(end); + AddReplacement(new Replacement(tok.StartIndex, "", ",")); + } + } + } + +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatContext.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatContext.cs new file mode 100644 index 00000000..61837b89 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatContext.cs @@ -0,0 +1,198 @@ +// +// 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.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using Babel.ParserGenerator; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + internal class FormatContext + { + private ReplacementQueue replacements = new ReplacementQueue(); + private string formattedSql; + + internal FormatContext(SqlScript sqlScript, FormatOptions options) + { + FormatOptions = options; + Script = sqlScript; + LoadKeywordIdentifiers(); + } + + internal SqlScript Script { get; private set; } + internal FormatOptions FormatOptions { get; set; } + internal int IndentLevel { get; set; } + internal HashSet KeywordIdentifiers { get; set; } + + private void LoadKeywordIdentifiers() + { + KeywordIdentifiers = new HashSet(); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_FROM); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_SELECT); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_TABLE); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_CREATE); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_USEDB); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_NOT); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_NULL); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_IDENTITY); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_ORDER); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_BY); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_DESC); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_ASC); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_GROUP); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_WHERE); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_JOIN); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_ON); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_UNION); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_ALL); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_EXCEPT); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_INTERSECT); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_INTO); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_DEFAULT); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_WITH); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_AS); + KeywordIdentifiers.Add(FormatterTokens.LEX_BATCH_SEPERATOR); + KeywordIdentifiers.Add(FormatterTokens.TOKEN_IS); + } + + public string FormattedSql + { + get + { + if (formattedSql == null) + { + DoFormatSql(); + } + return formattedSql; + } + } + + private void DoFormatSql() + { + StringBuilder code = new StringBuilder(Script.Sql); + foreach (Replacement r in Replacements) + { + r.Apply((int position, int length, string formattedText) => + { + if (length > 0) + { + if (formattedText.Length > 0) + { + code.Remove(position, length); + code.Insert(position, formattedText); + } + else + { + code.Remove(position, length); + } + } + else + { + if (formattedText.Length > 0) + { + code.Insert(position, formattedText); + } + else + { + throw new FormatException(SR.ErrorEmptyStringReplacement); + } + } + }); + } + formattedSql = code.ToString(); + } + + public ReplacementQueue Replacements + { + get + { + return replacements; + } + } + + internal void IncrementIndentLevel() + { + IndentLevel++; + } + + internal void DecrementIndentLevel() + { + if (IndentLevel == 0) + { + throw new FormatFailedException("can't decrement indent level. It is already 0."); + } + IndentLevel--; + } + + public string GetIndentString() + { + if (FormatOptions.UseTabs) + { + return new string('\t', IndentLevel); + } + else + { + return new string(' ', IndentLevel * FormatOptions.SpacesPerIndent); + } + } + + internal string GetTokenRangeAsOriginalString(int startTokenNumber, int endTokenNumber) + { + string sql = string.Empty; + if (endTokenNumber > startTokenNumber && startTokenNumber > -1 && endTokenNumber > -1) + { + sql = Script.TokenManager.GetText(startTokenNumber, endTokenNumber); + } + return sql; + } + + /// + /// Will apply any token-level formatting (e.g., uppercase/lowercase of keywords). + /// + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")] + internal void ProcessTokenRange(int startTokenNumber, int endTokenNumber) + { + + for (int i = startTokenNumber; i < endTokenNumber; i++) + { + string sql = GetTokenRangeAsOriginalString(i, i + 1); + + if (IsKeywordToken(Script.TokenManager.TokenList[i].TokenId)) + { + if (FormatOptions.UppercaseKeywords) + { + TokenData tok = Script.TokenManager.TokenList[i]; + Replacements.Add(new Replacement(tok.StartIndex, sql, sql.ToUpperInvariant())); + sql = sql.ToUpperInvariant(); + } + else if (FormatOptions.LowercaseKeywords) + { + TokenData tok = Script.TokenManager.TokenList[i]; + Replacements.Add(new Replacement(tok.StartIndex, sql, sql.ToLowerInvariant())); + sql = sql.ToLowerInvariant(); + } + } + } + + } + + internal void AppendTokenRangeAsString(int startTokenNumber, int endTokenNumber) + { + ProcessTokenRange(startTokenNumber, endTokenNumber); + } + + private bool IsKeywordToken(int tokenId) + { + return KeywordIdentifiers.Contains(tokenId); + } + + internal List CurrentColumnSpacingFormatDefinitions { get; set; } + + } + +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatFailedException.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatFailedException.cs new file mode 100644 index 00000000..c3896347 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatFailedException.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + public class FormatFailedException : Exception + { + public FormatFailedException() + : base() + { + } + + public FormatFailedException(string message, Exception exception) + : base(message, exception) + { + } + + + public FormatFailedException(string message) + : base(message) + { + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatOptions.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatOptions.cs new file mode 100644 index 00000000..3071c066 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatOptions.cs @@ -0,0 +1,171 @@ +// +// 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.ComponentModel; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + public enum CasingOptions { None, Uppercase, Lowercase }; + + /// + /// The supported options to use when formatting text + /// + public class FormatOptions : INotifyPropertyChanged + { + + private int spacesPerIndent; + private bool useTabs = false; + private bool encloseIdentifiersInSquareBrackets; + private bool placeCommasBeforeNextStatement; + private bool placeEachReferenceOnNewLineInQueryStatements; + private CasingOptions keywordCasing; + private CasingOptions datatypeCasing; + private bool alignColumnDefinitionsInColumns; + + internal FormatOptions() + { + SpacesPerIndent = 4; + UseTabs = false; + PlaceCommasBeforeNextStatement = false; + EncloseIdentifiersInSquareBrackets = false; + PlaceEachReferenceOnNewLineInQueryStatements = false; + } + + public int SpacesPerIndent + { + get { return spacesPerIndent; } + set { spacesPerIndent = value; + RaisePropertyChanged("SpacesPerIndent"); } + } + + public bool UseTabs + { + get { return useTabs; } + set + { + useTabs = value; + // raise UseTabs & UseSpaces property changed events + RaisePropertyChanged("UseTabs"); + RaisePropertyChanged("UseSpaces"); + } + } + + public bool UseSpaces + { + get { return !UseTabs; } + set { UseTabs = !value; } + } + + public bool EncloseIdentifiersInSquareBrackets + { + get { return encloseIdentifiersInSquareBrackets; } + set + { + encloseIdentifiersInSquareBrackets = value; + RaisePropertyChanged("EncloseIdentifiersInSquareBrackets"); + } + } + + public bool PlaceCommasBeforeNextStatement + { + get { return placeCommasBeforeNextStatement; } + set + { + placeCommasBeforeNextStatement = value; + RaisePropertyChanged("PlaceCommasBeforeNextStatement"); + } + } + + public bool PlaceEachReferenceOnNewLineInQueryStatements + { + get { return placeEachReferenceOnNewLineInQueryStatements; } + set + { + placeEachReferenceOnNewLineInQueryStatements = value; + RaisePropertyChanged("PlaceEachReferenceOnNewLineInQueryStatements"); + } + } + + public CasingOptions KeywordCasing + { + get { return keywordCasing; } + set + { + keywordCasing = value; + RaisePropertyChanged("KeywordCasing"); + } + } + + public bool UppercaseKeywords + { + get { return KeywordCasing == CasingOptions.Uppercase; } + } + public bool LowercaseKeywords + { + get { return KeywordCasing == CasingOptions.Lowercase; } + } + + public bool DoNotFormatKeywords + { + get { return KeywordCasing == CasingOptions.None; } + } + + public CasingOptions DatatypeCasing + { + get { return datatypeCasing; } + set + { + datatypeCasing = value; + RaisePropertyChanged("DatatypeCasing"); + } + } + + public bool UppercaseDataTypes + { + get { return DatatypeCasing == CasingOptions.Uppercase; } + } + public bool LowercaseDataTypes + { + get { return DatatypeCasing == CasingOptions.Lowercase; } + } + public bool DoNotFormatDataTypes + { + get { return DatatypeCasing == CasingOptions.None; } + } + + public bool AlignColumnDefinitionsInColumns + { + get { return alignColumnDefinitionsInColumns; } + set + { + alignColumnDefinitionsInColumns = value; + RaisePropertyChanged("AlignColumnDefinitionsInColumns"); + } + } + + private void RaisePropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public event PropertyChangedEventHandler PropertyChanged; + + public static void Copy(FormatOptions target, FormatOptions source) + { + target.AlignColumnDefinitionsInColumns = source.AlignColumnDefinitionsInColumns; + target.DatatypeCasing = source.DatatypeCasing; + target.EncloseIdentifiersInSquareBrackets = source.EncloseIdentifiersInSquareBrackets; + target.KeywordCasing = source.KeywordCasing; + target.PlaceCommasBeforeNextStatement = source.PlaceCommasBeforeNextStatement; + target.PlaceEachReferenceOnNewLineInQueryStatements = source.PlaceEachReferenceOnNewLineInQueryStatements; + target.SpacesPerIndent = source.SpacesPerIndent; + target.UseSpaces = source.UseSpaces; + target.UseTabs = source.UseTabs; + } + } + +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatterTokens.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatterTokens.cs new file mode 100644 index 00000000..38b1e008 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatterTokens.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.ComponentModel; +using Microsoft.SqlServer.Management.SqlParser.Parser; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + /// + /// Dynamically resolves the token IDs to match the values in the enum Microsoft.SqlServer.Management.SqlParser.Parser.Tokens. + /// This way, if the values in the enum change but their names remain the same + /// (when the Microsoft.SqlServer.Management.SqlParser.Parser.dll adds new tokens to the enum and is rebuilt), + /// the new values are retreived at runtime without having to rebuild Microsoft.SqlTools.ServiceLayer.Formatter.dll + /// + static class FormatterTokens + { + private static int ResolveTokenId(string tokenName) + { + EnumConverter converter = new EnumConverter(typeof(Tokens)); + return (int)converter.ConvertFromString(tokenName); + } + + public static readonly int TOKEN_FOR = ResolveTokenId("TOKEN_FOR"); + public static readonly int TOKEN_REPLICATION = ResolveTokenId("TOKEN_REPLICATION"); + public static readonly int TOKEN_ID = ResolveTokenId("TOKEN_ID"); + public static readonly int LEX_END_OF_LINE_COMMENT = ResolveTokenId("LEX_END_OF_LINE_COMMENT"); + public static readonly int TOKEN_FROM = ResolveTokenId("TOKEN_FROM"); + public static readonly int TOKEN_SELECT = ResolveTokenId("TOKEN_SELECT"); + public static readonly int TOKEN_TABLE = ResolveTokenId("TOKEN_TABLE"); + public static readonly int TOKEN_USEDB = ResolveTokenId("TOKEN_USEDB"); + public static readonly int TOKEN_NOT = ResolveTokenId("TOKEN_NOT"); + public static readonly int TOKEN_NULL = ResolveTokenId("TOKEN_NULL"); + public static readonly int TOKEN_IDENTITY = ResolveTokenId("TOKEN_IDENTITY"); + public static readonly int TOKEN_ORDER = ResolveTokenId("TOKEN_ORDER"); + public static readonly int TOKEN_BY = ResolveTokenId("TOKEN_BY"); + public static readonly int TOKEN_DESC = ResolveTokenId("TOKEN_DESC"); + public static readonly int TOKEN_ASC = ResolveTokenId("TOKEN_ASC"); + public static readonly int TOKEN_GROUP = ResolveTokenId("TOKEN_GROUP"); + public static readonly int TOKEN_WHERE = ResolveTokenId("TOKEN_WHERE"); + public static readonly int TOKEN_JOIN = ResolveTokenId("TOKEN_JOIN"); + public static readonly int TOKEN_ON = ResolveTokenId("TOKEN_ON"); + public static readonly int TOKEN_UNION = ResolveTokenId("TOKEN_UNION"); + public static readonly int TOKEN_ALL = ResolveTokenId("TOKEN_ALL"); + public static readonly int TOKEN_EXCEPT = ResolveTokenId("TOKEN_EXCEPT"); + public static readonly int TOKEN_INTERSECT = ResolveTokenId("TOKEN_INTERSECT"); + public static readonly int TOKEN_INTO = ResolveTokenId("TOKEN_INTO"); + public static readonly int TOKEN_DEFAULT = ResolveTokenId("TOKEN_DEFAULT"); + public static readonly int TOKEN_WITH = ResolveTokenId("TOKEN_WITH"); + public static readonly int TOKEN_AS = ResolveTokenId("TOKEN_AS"); + public static readonly int TOKEN_IS = ResolveTokenId("TOKEN_IS"); + public static readonly int TOKEN_BEGIN_CS = ResolveTokenId("TOKEN_BEGIN_CS"); + public static readonly int TOKEN_END_CS = ResolveTokenId("TOKEN_END_CS"); + public static readonly int LEX_BATCH_SEPERATOR = ResolveTokenId("LEX_BATCH_SEPERATOR"); + public static readonly int TOKEN_CREATE = ResolveTokenId("TOKEN_CREATE"); + public static readonly int LAST_TOKEN = ResolveTokenId("LAST_TOKEN"); + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatterUtilities.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatterUtilities.cs new file mode 100644 index 00000000..657f35b9 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatterUtilities.cs @@ -0,0 +1,77 @@ +// +// 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.Text; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + internal static class FormatterUtilities + { + internal static string StripAllWhitespace(string original, FormatContext context) + { + return String.Empty; + } + + internal static string NormalizeToOneSpace(string original, FormatContext context) + { + return " "; + } + + internal static string NormalizeNewLinesInWhitespace(string original, FormatContext context) + { + return NormalizeNewLinesInWhitespace(original, context, 0); + } + + internal static string NormalizeNewLinesEnsureOneNewLineMinimum(string original, FormatContext context) + { + return NormalizeNewLinesInWhitespace(original, context, 1); + } + + private static string NormalizeNewLinesInWhitespace(string original, FormatContext context, int minimumNewLines) + { + return NormalizeNewLinesInWhitespace(original, context, 1, () => { return original; }); + } + + internal static string NormalizeNewLinesOrCondenseToOneSpace(string original, FormatContext context) + { + return NormalizeNewLinesOrCondenseToNSpaces(original, context, 1); + } + + internal static string NormalizeNewLinesOrCondenseToNSpaces(string original, FormatContext context, int nSpaces) + { + return NormalizeNewLinesInWhitespace(original, context, 0, () => { return new String(' ', nSpaces); }); + } + + private static string NormalizeNewLinesInWhitespace(string original, FormatContext context, int minimumNewLines, Func noNewLinesProcessor) + { + int nNewLines = 0; + int idx = original.IndexOf(Environment.NewLine, StringComparison.OrdinalIgnoreCase); + while (idx > -1) + { + ++nNewLines; + idx = original.IndexOf(Environment.NewLine, idx + 1, StringComparison.OrdinalIgnoreCase); + } + + StringBuilder sb = new StringBuilder(); + nNewLines = Math.Max(minimumNewLines, nNewLines); + for (int i = 0; i < nNewLines; i++) + { + sb.Append(Environment.NewLine); + } + sb.Append(context.GetIndentString()); + + if (nNewLines > 0) + { + return sb.ToString(); + } + else + { + return noNewLinesProcessor(); + } + } + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatterVisitor.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatterVisitor.cs new file mode 100644 index 00000000..7ff83662 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatterVisitor.cs @@ -0,0 +1,168 @@ +// +// 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.Diagnostics; +using System.Globalization; +using System.Linq; +using Microsoft.SqlServer.Management.SqlParser.Parser; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; +using Microsoft.SqlTools.ServiceLayer.Extensibility; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + /// + /// The main entry point for our formatter implementation, via the method. + /// This converts a text string into a parsed AST using the Intellisense parser. + /// It then uses the Visitor pattern to find each element in the tree and determine if any edits are needed based on + /// All edits are applied after the entire AST has been visited using an algorithm that keeps track of index changes caused by previous updates. This allows + /// us to apply multiple edits to a text string in one sweep. + /// + /// A note on the implementation: All of the override nodes in the Intellisense AST are defined here, and routed to the Format method which looks up a matching + /// formatter to handle them. Any entry not explicitly formatted will use the no-op formatter which passes through the text unchanged. + /// + internal partial class FormatterVisitor : SqlCodeObjectVisitor + { + private readonly IMultiServiceProvider serviceProvider; + + public FormatterVisitor(FormatContext context, IMultiServiceProvider serviceProvider) + : base() + { + Context = context; + this.serviceProvider = serviceProvider; + } + + private void Format(T codeObject) where T : SqlCodeObject + { + ASTNodeFormatter f = GetFormatter(codeObject); + f.Format(); + } + + private ASTNodeFormatter GetFormatter(T codeObject) where T:SqlCodeObject + { + Type astType = typeof(T); + ASTNodeFormatter formatter; + + var formatterFactory = serviceProvider.GetServices().FirstOrDefault(f => astType.Equals(f.SupportedNodeType)); + if (formatterFactory != null) + { + formatter = formatterFactory.Create(this, codeObject); + } + else + { + formatter = new NoOpFormatter(this, codeObject); + } + + return formatter; + } + + public FormatContext Context { get; private set; } + + public void VerifyFormat() + { + ParseResult result = Parser.Parse(Context.FormattedSql); + SqlScript newScript = result.Script; + VerifyTokenStreamsOnlyDifferByWhitespace(Context.Script, newScript); + } + + internal static bool IsTokenWhitespaceOrComma(SqlScript script, int tokenIndex) + { + int tokenId = script.TokenManager.TokenList[tokenIndex].TokenId; + return script.TokenManager.IsTokenWhitespace(tokenId) || (tokenId == 44); + } + + internal static bool IsTokenWhitespaceOrComment(SqlScript script, int tokenIndex) + { + int tokenId = script.TokenManager.TokenList[tokenIndex].TokenId; + + return script.TokenManager.IsTokenWhitespace(tokenId) || script.TokenManager.IsTokenComment(tokenId); + } + + /// + /// Checks that the token streams of two SqlScript objects differ only by whitespace tokens or + /// by the relative positioning of commas and comments. The important rule enforced is that there are + /// no changes in relative positioning which involve tokens other than commas, comments or whitespaces. + /// + /// SQL script containing the first token stream. + /// SQL script containing the second token stream. + public static void VerifyTokenStreamsOnlyDifferByWhitespace(SqlScript script1, SqlScript script2) + { + // We break down the relative positioning problem into assuring that the token streams have identical ids + // both when we ignore whitespaces and commas as well as when we ignore whitespaces and comments + VerifyTokenStreamsOnlyDifferBy(script1, script2, IsTokenWhitespaceOrComma); + VerifyTokenStreamsOnlyDifferBy(script1, script2, IsTokenWhitespaceOrComment); + } + + internal delegate bool IgnoreToken(SqlScript script, int tokenIndex); + + public static void VerifyTokenStreamsOnlyDifferBy(SqlScript script1, SqlScript script2, IgnoreToken ignoreToken ) + { + int t1 = 0; + int t2 = 0; + + while (t1 < script1.TokenManager.Count && t2 < script2.TokenManager.Count) + { + // advance t1 until it is pointing at a non-whitespace token + while (t1 < script1.TokenManager.Count && ignoreToken(script1, t1)) + { + ++t1; + } + + // advance t2 until it is pointing at a non-whitespace token + while (t2 < script2.TokenManager.Count && ignoreToken(script2, t2)) + { + ++t2; + } + + if (t1 >= script1.TokenManager.Count || t2 >= script2.TokenManager.Count) + { + break; + } + + + // + // TODO: need special logic here to deal with the placement of commas + // + + // verify the tokens are equal + if (script1.TokenManager.TokenList[t1].TokenId != script2.TokenManager.TokenList[t2].TokenId) + { + string msg = "The comparison failed between tokens at {0} & {1}. The token IDs were {2} and {3} respectively. Script1 = {4}. Script2 = {5}"; + msg = string.Format(CultureInfo.CurrentCulture, msg, t1, t2, script1.TokenManager.TokenList[t1].TokenId, script2.TokenManager.TokenList[t2].TokenId, script1.Sql, script2.Sql); + throw new FormatFailedException(msg); + } + + ++t1; + ++t2; + } + + // one of the streams is exhausted, verify that the only tokens left in the other stream are whitespace tokens + Debug.Assert(t1 >= script1.TokenManager.Count || t2 >= script2.TokenManager.Count, "expected to be at the end of one of the token's streams"); + int t = t1; + SqlScript s = script1; + if (t2 < script2.TokenManager.Count) + { + Debug.Assert(t1 >= script1.TokenManager.Count, "expected to be at end of script1's token stream"); + t = t2; + s = script2; + } + + while (t < s.TokenManager.Count) + { + if (!ignoreToken(s, t)) + { + string msg = "Unexpected non-whitespace token at index {0}, token ID {1}"; + msg = string.Format(CultureInfo.CurrentCulture, msg, t, s.TokenManager.TokenList[t].TokenId); + throw new FormatFailedException(msg); + } + } + } + + + + + } + +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatterVisitor_SqlCodeObjectVisitor.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatterVisitor_SqlCodeObjectVisitor.cs new file mode 100644 index 00000000..7c406d00 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/FormatterVisitor_SqlCodeObjectVisitor.cs @@ -0,0 +1,300 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Diagnostics.CodeAnalysis; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + // Any new SqlCodeObject types should have a Visit method added, and this class should then be updated with a matching + // Visit implementation that routes to the Format method. + [SuppressMessage("Microsoft.Maintainability","CA1506:AvoidExcessiveClassCoupling")] + partial class FormatterVisitor : SqlCodeObjectVisitor + { + public override void Visit(SqlAggregateFunctionCallExpression codeObject) { Format(codeObject); } + public override void Visit(SqlAllAnyComparisonBooleanExpression codeObject) { Format(codeObject); } + public override void Visit(SqlAllowPageLocksIndexOption codeObject) { Format(codeObject); } + public override void Visit(SqlAllowRowLocksIndexOption codeObject) { Format(codeObject); } + public override void Visit(SqlAlterFunctionStatement codeObject) { Format(codeObject); } + public override void Visit(SqlAlterProcedureStatement codeObject) { Format(codeObject); } + public override void Visit(SqlAlterTriggerStatement codeObject) { Format(codeObject); } + public override void Visit(SqlAlterViewStatement codeObject) { Format(codeObject); } + public override void Visit(SqlAssignment codeObject) { Format(codeObject); } + public override void Visit(SqlBackupCertificateStatement codeObject) { Format(codeObject); } + public override void Visit(SqlBackupDatabaseStatement codeObject) { Format(codeObject); } + public override void Visit(SqlBackupLogStatement codeObject) { Format(codeObject); } + public override void Visit(SqlBackupMasterKeyStatement codeObject) { Format(codeObject); } + public override void Visit(SqlBackupServiceMasterKeyStatement codeObject) { Format(codeObject); } + public override void Visit(SqlBackupTableStatement codeObject) { Format(codeObject); } + public override void Visit(SqlBatch codeObject) { Format(codeObject); } + public override void Visit(SqlBetweenBooleanExpression codeObject) { Format(codeObject); } + public override void Visit(SqlBinaryBooleanExpression codeObject) { Format(codeObject); } + public override void Visit(SqlBinaryFilterExpression codeObject) { Format(codeObject); } + public override void Visit(SqlBinaryQueryExpression codeObject) { Format(codeObject); } + public override void Visit(SqlBinaryScalarExpression codeObject) { Format(codeObject); } + public override void Visit(SqlBooleanExpression codeObject) { Format(codeObject); } + public override void Visit(SqlBooleanFilterExpression codeObject) { Format(codeObject); } + public override void Visit(SqlBreakStatement codeObject) { Format(codeObject); } + public override void Visit(SqlBuiltinScalarFunctionCallExpression codeObject) { Format(codeObject); } + public override void Visit(SqlCastExpression codeObject) { Format(codeObject); } + public override void Visit(SqlChangeTrackingContext codeObject) { Format(codeObject); } + public override void Visit(SqlCheckConstraint codeObject) { Format(codeObject); } + public override void Visit(SqlClrAssemblySpecifier codeObject) { Format(codeObject); } + public override void Visit(SqlClrClassSpecifier codeObject) { Format(codeObject); } + public override void Visit(SqlClrFunctionBodyDefinition codeObject) { Format(codeObject); } + public override void Visit(SqlClrMethodSpecifier codeObject) { Format(codeObject); } + public override void Visit(SqlCollateScalarExpression codeObject) { Format(codeObject); } + public override void Visit(SqlCollation codeObject) { Format(codeObject); } + public override void Visit(SqlColumnAssignment codeObject) { Format(codeObject); } + public override void Visit(SqlColumnDefinition codeObject) { Format(codeObject); } + public override void Visit(SqlColumnIdentity codeObject) { Format(codeObject); } + public override void Visit(SqlColumnRefExpression codeObject) { Format(codeObject); } + public override void Visit(SqlCommentStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCommonTableExpression codeObject) { Format(codeObject); } + public override void Visit(SqlComparisonBooleanExpression codeObject) { Format(codeObject); } + public override void Visit(SqlCompoundStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCompressionPartitionRange codeObject) { Format(codeObject); } + public override void Visit(SqlComputedColumnDefinition codeObject) { Format(codeObject); } + public override void Visit(SqlConditionClause codeObject) { Format(codeObject); } + public override void Visit(SqlConstraint codeObject) { Format(codeObject); } + public override void Visit(SqlContinueStatement codeObject) { Format(codeObject); } + public override void Visit(SqlConvertExpression codeObject) { Format(codeObject); } + public override void Visit(SqlCreateFunctionStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateIndexStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateLoginFromAsymKeyStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateLoginFromCertificateStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateLoginFromWindowsStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateLoginWithPasswordStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateProcedureStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateRoleStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateSchemaStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateSynonymStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateTableStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateTriggerStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateUserDefinedDataTypeStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateUserDefinedTableTypeStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateUserDefinedTypeStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateUserFromAsymKeyStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateUserFromCertificateStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateUserFromLoginStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateUserOption codeObject) { Format(codeObject); } + public override void Visit(SqlCreateUserStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateUserWithImplicitAuthenticationStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateUserWithoutLoginStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCreateViewStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCubeGroupByItem codeObject) { Format(codeObject); } + public override void Visit(SqlCursorDeclareStatement codeObject) { Format(codeObject); } + public override void Visit(SqlCursorOption codeObject) { Format(codeObject); } + public override void Visit(SqlCursorVariableAssignment codeObject) { Format(codeObject); } + public override void Visit(SqlCursorVariableRefExpression codeObject) { Format(codeObject); } + public override void Visit(SqlDataCompressionIndexOption codeObject) { Format(codeObject); } + public override void Visit(SqlDataType codeObject) { Format(codeObject); } + public override void Visit(SqlDataTypeSpecification codeObject) { Format(codeObject); } + public override void Visit(SqlDBCCStatement codeObject) { Format(codeObject); } + public override void Visit(SqlDdlTriggerDefinition codeObject) { Format(codeObject); } + public override void Visit(SqlDefaultConstraint codeObject) { Format(codeObject); } + public override void Visit(SqlDefaultValuesInsertMergeActionSource codeObject) { Format(codeObject); } + public override void Visit(SqlDefaultValuesInsertSource codeObject) { Format(codeObject); } + public override void Visit(SqlDeleteMergeAction codeObject) { Format(codeObject); } + public override void Visit(SqlDeleteSpecification codeObject) { Format(codeObject); } + public override void Visit(SqlDeleteStatement codeObject) { Format(codeObject); } + public override void Visit(SqlDenyStatement codeObject) { Format(codeObject); } + public override void Visit(SqlDerivedTableExpression codeObject) { Format(codeObject); } + public override void Visit(SqlDmlSpecificationTableSource codeObject) { Format(codeObject); } + public override void Visit(SqlDmlTriggerDefinition codeObject) { Format(codeObject); } + public override void Visit(SqlDropAggregateStatement codeObject) { Format(codeObject); } + public override void Visit(SqlDropDatabaseStatement codeObject) { Format(codeObject); } + public override void Visit(SqlDropDefaultStatement codeObject) { Format(codeObject); } + public override void Visit(SqlDropExistingIndexOption codeObject) { Format(codeObject); } + public override void Visit(SqlDropFunctionStatement codeObject) { Format(codeObject); } + public override void Visit(SqlDropLoginStatement codeObject) { Format(codeObject); } + public override void Visit(SqlDropProcedureStatement codeObject) { Format(codeObject); } + public override void Visit(SqlDropRuleStatement codeObject) { Format(codeObject); } + public override void Visit(SqlDropSchemaStatement codeObject) { Format(codeObject); } + public override void Visit(SqlDropSynonymStatement codeObject) { Format(codeObject); } + public override void Visit(SqlDropTableStatement codeObject) { Format(codeObject); } + public override void Visit(SqlDropTriggerStatement codeObject) { Format(codeObject); } + public override void Visit(SqlDropTypeStatement codeObject) { Format(codeObject); } + public override void Visit(SqlDropUserStatement codeObject) { Format(codeObject); } + public override void Visit(SqlDropViewStatement codeObject) { Format(codeObject); } + public override void Visit(SqlExecuteAsClause codeObject) { Format(codeObject); } + public override void Visit(SqlExecuteModuleStatement codeObject) { Format(codeObject); } + public override void Visit(SqlExistsBooleanExpression codeObject) { Format(codeObject); } + public override void Visit(SqlFillFactorIndexOption codeObject) { Format(codeObject); } + public override void Visit(SqlFilterClause codeObject) { Format(codeObject); } + public override void Visit(SqlForBrowseClause codeObject) { Format(codeObject); } + public override void Visit(SqlForeignKeyConstraint codeObject) { Format(codeObject); } + public override void Visit(SqlForXmlAutoClause codeObject) { Format(codeObject); } + public override void Visit(SqlForXmlClause codeObject) { Format(codeObject); } + public override void Visit(SqlForXmlDirective codeObject) { Format(codeObject); } + public override void Visit(SqlForXmlExplicitClause codeObject) { Format(codeObject); } + public override void Visit(SqlForXmlPathClause codeObject) { Format(codeObject); } + public override void Visit(SqlForXmlRawClause codeObject) { Format(codeObject); } + public override void Visit(SqlFromClause codeObject) { Format(codeObject); } + public override void Visit(SqlFullTextBooleanExpression codeObject) { Format(codeObject); } + public override void Visit(SqlFullTextColumn codeObject) { Format(codeObject); } + public override void Visit(SqlFunctionDefinition codeObject) { Format(codeObject); } + public override void Visit(SqlGlobalScalarVariableRefExpression codeObject) { Format(codeObject); } + public override void Visit(SqlGrandTotalGroupByItem codeObject) { Format(codeObject); } + public override void Visit(SqlGrandTotalGroupingSet codeObject) { Format(codeObject); } + public override void Visit(SqlGrantStatement codeObject) { Format(codeObject); } + public override void Visit(SqlGroupByClause codeObject) { Format(codeObject); } + public override void Visit(SqlGroupBySets codeObject) { Format(codeObject); } + public override void Visit(SqlGroupingSetItemsCollection codeObject) { Format(codeObject); } + public override void Visit(SqlHavingClause codeObject) { Format(codeObject); } + public override void Visit(SqlIdentifier codeObject) { Format(codeObject); } + public override void Visit(SqlIdentityFunctionCallExpression codeObject) { Format(codeObject); } + public override void Visit(SqlIfElseStatement codeObject) { Format(codeObject); } + public override void Visit(SqlIgnoreDupKeyIndexOption codeObject) { Format(codeObject); } + public override void Visit(SqlInBooleanExpression codeObject) { Format(codeObject); } + public override void Visit(SqlInBooleanExpressionCollectionValue codeObject) { Format(codeObject); } + public override void Visit(SqlInBooleanExpressionQueryValue codeObject) { Format(codeObject); } + public override void Visit(SqlIndexedColumn codeObject) { Format(codeObject); } + public override void Visit(SqlIndexHint codeObject) { Format(codeObject); } + public override void Visit(SqlIndexOption codeObject) { Format(codeObject); } + public override void Visit(SqlInlineFunctionBodyDefinition codeObject) { Format(codeObject); } + public override void Visit(SqlInlineTableRelationalFunctionDefinition codeObject) { Format(codeObject); } + public override void Visit(SqlInlineTableVariableDeclaration codeObject) { Format(codeObject); } + public override void Visit(SqlInlineTableVariableDeclareStatement codeObject) { Format(codeObject); } + public override void Visit(SqlInsertMergeAction codeObject) { Format(codeObject); } + public override void Visit(SqlInsertSource codeObject) { Format(codeObject); } + public override void Visit(SqlInsertSpecification codeObject) { Format(codeObject); } + public override void Visit(SqlInsertStatement codeObject) { Format(codeObject); } + public override void Visit(SqlIntoClause codeObject) { Format(codeObject); } + public override void Visit(SqlIsNullBooleanExpression codeObject) { Format(codeObject); } + public override void Visit(SqlLargeDataStorageInformation codeObject) { Format(codeObject); } + public override void Visit(SqlLikeBooleanExpression codeObject) { Format(codeObject); } + public override void Visit(SqlLiteralExpression codeObject) { Format(codeObject); } + public override void Visit(SqlLoginPassword codeObject) { Format(codeObject); } + public override void Visit(SqlMaxDegreeOfParallelismIndexOption codeObject) { Format(codeObject); } + public override void Visit(SqlMergeActionClause codeObject) { Format(codeObject); } + public override void Visit(SqlMergeSpecification codeObject) { Format(codeObject); } + public override void Visit(SqlMergeStatement codeObject) { Format(codeObject); } + public override void Visit(SqlModuleArgument codeObject) { Format(codeObject); } + public override void Visit(SqlModuleCalledOnNullInputOption codeObject) { Format(codeObject); } + public override void Visit(SqlModuleEncryptionOption codeObject) { Format(codeObject); } + public override void Visit(SqlModuleExecuteAsOption codeObject) { Format(codeObject); } + public override void Visit(SqlModuleOption codeObject) { Format(codeObject); } + public override void Visit(SqlModuleRecompileOption codeObject) { Format(codeObject); } + public override void Visit(SqlModuleReturnsNullOnNullInputOption codeObject) { Format(codeObject); } + public override void Visit(SqlModuleSchemaBindingOption codeObject) { Format(codeObject); } + public override void Visit(SqlModuleViewMetadataOption codeObject) { Format(codeObject); } + public override void Visit(SqlMultistatementFunctionBodyDefinition codeObject) { Format(codeObject); } + public override void Visit(SqlMultistatementTableRelationalFunctionDefinition codeObject) { Format(codeObject); } + public override void Visit(SqlNotBooleanExpression codeObject) { Format(codeObject); } + public override void Visit(SqlObjectIdentifier codeObject) { Format(codeObject); } + public override void Visit(SqlObjectReference codeObject) { Format(codeObject); } + public override void Visit(SqlOnlineIndexOption codeObject) { Format(codeObject); } + public override void Visit(SqlOrderByClause codeObject) { Format(codeObject); } + public override void Visit(SqlOrderByItem codeObject) { Format(codeObject); } + public override void Visit(SqlOutputClause codeObject) { Format(codeObject); } + public override void Visit(SqlOutputIntoClause codeObject) { Format(codeObject); } + public override void Visit(SqlPadIndexOption codeObject) { Format(codeObject); } + public override void Visit(SqlParameterDeclaration codeObject) { Format(codeObject); } + public override void Visit(SqlPivotClause codeObject) { Format(codeObject); } + public override void Visit(SqlPivotTableExpression codeObject) { Format(codeObject); } + public override void Visit(SqlPrimaryKeyConstraint codeObject) { Format(codeObject); } + public override void Visit(SqlProcedureDefinition codeObject) { Format(codeObject); } + public override void Visit(SqlQualifiedJoinTableExpression codeObject) { Format(codeObject); } + public override void Visit(SqlQueryExpression codeObject) { Format(codeObject); } + public override void Visit(SqlQuerySpecification codeObject) { Format(codeObject); } + public override void Visit(SqlQueryWithClause codeObject) { Format(codeObject); } + public override void Visit(SqlRestoreDatabaseStatement codeObject) { Format(codeObject); } + public override void Visit(SqlRestoreInformationStatement codeObject) { Format(codeObject); } + public override void Visit(SqlRestoreLogStatement codeObject) { Format(codeObject); } + public override void Visit(SqlRestoreMasterKeyStatement codeObject) { Format(codeObject); } + public override void Visit(SqlRestoreServiceMasterKeyStatement codeObject) { Format(codeObject); } + public override void Visit(SqlRestoreTableStatement codeObject) { Format(codeObject); } + public override void Visit(SqlReturnStatement codeObject) { Format(codeObject); } + public override void Visit(SqlRevokeStatement codeObject) { Format(codeObject); } + public override void Visit(SqlRollupGroupByItem codeObject) { Format(codeObject); } + public override void Visit(SqlRowConstructorExpression codeObject) { Format(codeObject); } + public override void Visit(SqlScalarClrFunctionDefinition codeObject) { Format(codeObject); } + public override void Visit(SqlScalarExpression codeObject) { Format(codeObject); } + public override void Visit(SqlScalarFunctionReturnType codeObject) { Format(codeObject); } + public override void Visit(SqlScalarRefExpression codeObject) { Format(codeObject); } + public override void Visit(SqlScalarRelationalFunctionDefinition codeObject) { Format(codeObject); } + public override void Visit(SqlScalarSubQueryExpression codeObject) { Format(codeObject); } + public override void Visit(SqlScalarVariableAssignment codeObject) { Format(codeObject); } + public override void Visit(SqlScalarVariableRefExpression codeObject) { Format(codeObject); } + public override void Visit(SqlScript codeObject) { Format(codeObject); } + public override void Visit(SqlSearchedCaseExpression codeObject) { Format(codeObject); } + public override void Visit(SqlSearchedWhenClause codeObject) { Format(codeObject); } + public override void Visit(SqlSelectClause codeObject) { Format(codeObject); } + public override void Visit(SqlSelectIntoClause codeObject) { Format(codeObject); } + public override void Visit(SqlSelectScalarExpression codeObject) { Format(codeObject); } + public override void Visit(SqlSelectSpecification codeObject) { Format(codeObject); } + public override void Visit(SqlSelectSpecificationInsertSource codeObject) { Format(codeObject); } + public override void Visit(SqlSelectStarExpression codeObject) { Format(codeObject); } + public override void Visit(SqlSelectStatement codeObject) { Format(codeObject); } + public override void Visit(SqlSelectVariableAssignmentExpression codeObject) { Format(codeObject); } + public override void Visit(SqlSetAssignmentStatement codeObject) { Format(codeObject); } + public override void Visit(SqlSetClause codeObject) { Format(codeObject); } + public override void Visit(SqlSetStatement codeObject) { Format(codeObject); } + public override void Visit(SqlSimpleCaseExpression codeObject) { Format(codeObject); } + public override void Visit(SqlSimpleGroupByItem codeObject) { Format(codeObject); } + public override void Visit(SqlSimpleOrderByClause codeObject) { Format(codeObject); } + public override void Visit(SqlSimpleOrderByItem codeObject) { Format(codeObject); } + public override void Visit(SqlSimpleWhenClause codeObject) { Format(codeObject); } + public override void Visit(SqlSortedDataIndexOption codeObject) { Format(codeObject); } + public override void Visit(SqlSortedDataReorgIndexOption codeObject) { Format(codeObject); } + public override void Visit(SqlSortInTempDbIndexOption codeObject) { Format(codeObject); } + public override void Visit(SqlStatement codeObject) { Format(codeObject); } + public override void Visit(SqlStatisticsNoRecomputeIndexOption codeObject) { Format(codeObject); } + public override void Visit(SqlStatisticsOnlyIndexOption codeObject) { Format(codeObject); } + public override void Visit(SqlStorageSpecification codeObject) { Format(codeObject); } + public override void Visit(SqlTableClrFunctionDefinition codeObject) { Format(codeObject); } + public override void Visit(SqlTableConstructorExpression codeObject) { Format(codeObject); } + public override void Visit(SqlTableConstructorInsertSource codeObject) { Format(codeObject); } + public override void Visit(SqlTableDefinition codeObject) { Format(codeObject); } + public override void Visit(SqlTableExpression codeObject) { Format(codeObject); } + public override void Visit(SqlTableFunctionReturnType codeObject) { Format(codeObject); } + public override void Visit(SqlTableHint codeObject) { Format(codeObject); } + public override void Visit(SqlTableRefExpression codeObject) { Format(codeObject); } + public override void Visit(SqlTableUdtInstanceMethodExpression codeObject) { Format(codeObject); } + public override void Visit(SqlTableValuedFunctionRefExpression codeObject) { Format(codeObject); } + public override void Visit(SqlTableVariableRefExpression codeObject) { Format(codeObject); } + public override void Visit(SqlTargetTableExpression codeObject) { Format(codeObject); } + public override void Visit(SqlTopSpecification codeObject) { Format(codeObject); } + public override void Visit(SqlTriggerAction codeObject) { Format(codeObject); } + public override void Visit(SqlTriggerDefinition codeObject) { Format(codeObject); } + public override void Visit(SqlTriggerEvent codeObject) { Format(codeObject); } + public override void Visit(SqlTryCatchStatement codeObject) { Format(codeObject); } + public override void Visit(SqlUdtInstanceDataMemberExpression codeObject) { Format(codeObject); } + public override void Visit(SqlUdtInstanceMethodExpression codeObject) { Format(codeObject); } + public override void Visit(SqlUdtStaticDataMemberExpression codeObject) { Format(codeObject); } + public override void Visit(SqlUdtStaticMethodExpression codeObject) { Format(codeObject); } + public override void Visit(SqlUnaryScalarExpression codeObject) { Format(codeObject); } + public override void Visit(SqlUniqueConstraint codeObject) { Format(codeObject); } + public override void Visit(SqlUnpivotClause codeObject) { Format(codeObject); } + public override void Visit(SqlUnpivotTableExpression codeObject) { Format(codeObject); } + public override void Visit(SqlUnqualifiedJoinTableExpression codeObject) { Format(codeObject); } + public override void Visit(SqlUpdateBooleanExpression codeObject) { Format(codeObject); } + public override void Visit(SqlUpdateMergeAction codeObject) { Format(codeObject); } + public override void Visit(SqlUpdateSpecification codeObject) { Format(codeObject); } + public override void Visit(SqlUpdateStatement codeObject) { Format(codeObject); } + public override void Visit(SqlUserDefinedScalarFunctionCallExpression codeObject) { Format(codeObject); } + public override void Visit(SqlUseStatement codeObject) { Format(codeObject); } + public override void Visit(SqlValuesInsertMergeActionSource codeObject) { Format(codeObject); } + public override void Visit(SqlVariableColumnAssignment codeObject) { Format(codeObject); } + public override void Visit(SqlVariableDeclaration codeObject) { Format(codeObject); } + public override void Visit(SqlVariableDeclareStatement codeObject) { Format(codeObject); } + public override void Visit(SqlViewDefinition codeObject) { Format(codeObject); } + public override void Visit(SqlWhereClause codeObject) { Format(codeObject); } + public override void Visit(SqlWhileStatement codeObject) { Format(codeObject); } + public override void Visit(SqlXmlNamespacesDeclaration codeObject) { Format(codeObject); } + public override void Visit(SqlAtTimeZoneExpression codeObject) { Format(codeObject); } + public override void Visit(SqlBucketCountIndexOption codeObject) { Format(codeObject); } + public override void Visit(SqlCompressionDelayIndexOption codeObject) { Format(codeObject); } + public override void Visit(SqlCreateUserFromExternalProviderStatement codeObject) { Format(codeObject); } + public override void Visit(SqlDropSecurityPolicyStatement codeObject) { Format(codeObject); } + public override void Visit(SqlDropSequenceStatement codeObject) { Format(codeObject); } + public override void Visit(SqlInlineIndexConstraint codeObject) { Format(codeObject); } + public override void Visit(SqlModuleNativeCompilationOption codeObject) { Format(codeObject); } + public override void Visit(SqlStatisticsIncrementalIndexOption codeObject) { Format(codeObject); } + public override void Visit(SqlTemporalPeriodDefinition codeObject) { Format(codeObject); } + } + +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/NoOpFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/NoOpFormatter.cs new file mode 100644 index 00000000..0b3f319d --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/NoOpFormatter.cs @@ -0,0 +1,20 @@ +// +// 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.Composition; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + internal class NoOpFormatter : ASTNodeFormatterT + { + public NoOpFormatter(FormatterVisitor visitor, SqlCodeObject codeObject) + : base(visitor, codeObject) + { + + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/PaddedSpaceSeparatedListFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/PaddedSpaceSeparatedListFormatter.cs new file mode 100644 index 00000000..1677a4f7 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/PaddedSpaceSeparatedListFormatter.cs @@ -0,0 +1,74 @@ +// +// 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.Collections.Generic; +using System.Composition; +using System.Diagnostics; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + internal class PaddedSpaceSeparatedListFormatter : SpaceSeparatedListFormatter + { + private List ColumnSpacingDefinitions { get; set; } + private int nextColumn = 0; + + + internal PaddedSpaceSeparatedListFormatter(FormatterVisitor visitor, SqlCodeObject codeObject, List spacingDefinitions, bool incrementIndentLevelOnPrefixRegion) + : base(visitor, codeObject, incrementIndentLevelOnPrefixRegion) + { + ColumnSpacingDefinitions = spacingDefinitions; + } + + internal override void ProcessInterChildRegion(SqlCodeObject previousChild, SqlCodeObject nextChild) + { + Validate.IsNotNull(nameof(previousChild), previousChild); + Validate.IsNotNull(nameof(nextChild), nextChild); + + // first, figure out how big to make the pad + int padLength = 1; + if (ColumnSpacingDefinitions != null && nextColumn < ColumnSpacingDefinitions.Count) + { + if (previousChild.GetType() == ColumnSpacingDefinitions[nextColumn].PreviousType && + (ColumnSpacingDefinitions[nextColumn].NextType == null || nextChild.GetType() == ColumnSpacingDefinitions[nextColumn].NextType)) + { + string text = previousChild.TokenManager.GetText(previousChild.Position.startTokenNumber, previousChild.Position.endTokenNumber); + int stringLength = text.Length; + padLength = ColumnSpacingDefinitions[nextColumn].PaddedLength - stringLength; + + Debug.Assert(padLength > 0, "unexpected value for Pad Length"); + padLength = Math.Max(padLength, 1); + + ++nextColumn; + } + } + // next, normalize the tokens + int start = previousChild.Position.endTokenNumber; + int end = nextChild.Position.startTokenNumber; + + for (int i = start; i < end; i++) + { + SimpleProcessToken(i, (string original, FormatContext context) => { return FormatterUtilities.NormalizeNewLinesOrCondenseToNSpaces(original, context, padLength); }); + } + + } + + internal class ColumnSpacingFormatDefinition + { + internal ColumnSpacingFormatDefinition(Type previousType, Type nextType, int padLength) + { + PreviousType = previousType; + NextType = nextType; + PaddedLength = padLength; + } + + internal Type PreviousType { get; private set; } + internal Type NextType { get; private set; } + internal int PaddedLength { get; private set; } + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/Replacement.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/Replacement.cs new file mode 100644 index 00000000..79f50623 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/Replacement.cs @@ -0,0 +1,85 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + /// + /// Describes a string editing action which requests that a particular + /// substring found at a given location be replaced by another string + /// + public class Replacement + { + public Replacement(int startIndex, string oldValue, string newValue) + { + StartIndex = startIndex; + OldValue = oldValue; + NewValue = newValue; + } + + public int StartIndex { get; private set; } + public string OldValue { get; private set; } + public string NewValue { get; private set; } + + public int EndIndex + { + get + { + return StartIndex + OldValue.Length; + } + } + + /// + /// Checks whether the replacement will have any effect. + /// + /// + internal bool IsIdentity() + { + return OldValue.Equals(NewValue); + } + + /// + /// Reports the relative change in text length (number of characters) + /// between the initial and the formatted code introduced by this + /// particular replacement. + /// + public int InducedOffset + { + get + { + return NewValue.Length - OldValue.Length; + } + } + + /// + /// Replacements will often change the length of the code, making + /// indexing relative to the original text ambiguous. The CumulatedOffset + /// can be used to adjust the relative indexing between the original and the + /// edited text as perceived at the start of this replacement and help + /// compensate for the difference. + /// + public int CumulativeOffset { set; private get; } + + /// + /// A delegate responsible for applying the replacement. Each application assumes + /// nothing about other replacements which might have taken place before or which + /// might take place after the current one. + /// + /// Position of the begining of the replacement relative to the beginig of the character stream. + /// The number of consecutive characters which are to be replaced. + /// The characters which are to replace the old ones. Note that the length of this string might be greater or smaller than the number of replaced characters. + public delegate void OnReplace(int pos, int len, string with); + + /// + /// Applies a replacement action according to a given strategy defined by the delegate procedure. + /// + /// This delegate function implements the strategy for applying the replacement. + public void Apply(OnReplace replace) + { + replace(StartIndex + CumulativeOffset, OldValue.Length, NewValue); + } + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/ReplacementQueue.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/ReplacementQueue.cs new file mode 100644 index 00000000..e4acebab --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/ReplacementQueue.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Collections.Generic; +using System.Collections; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + internal class ReplacementQueue : IEnumerable + { + internal int offset = 0; + + private Queue Replacements { get; set; } + + public ReplacementQueue() + { + Replacements = new Queue(); + } + + /// + /// Adds a replace action to the queue and adjusts its absolute + /// offset to reflect the global indexing after applying the replacements + /// in the queue. + /// + /// NOTE: The method assumes the replacements occur in front-to-back order + /// and that they never overlap. + /// + /// + /// The latest replacement to be added to the queue. + public void Add(Replacement r) + { + if (!r.IsIdentity()) + { + r.CumulativeOffset = offset; + Replacements.Enqueue(r); + offset += r.InducedOffset; + } + } + + + IEnumerator IEnumerable.GetEnumerator() + { + return Replacements.GetEnumerator(); + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SpaceSeparatedListFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SpaceSeparatedListFormatter.cs new file mode 100644 index 00000000..56fcbf67 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SpaceSeparatedListFormatter.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Composition; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + internal class SpaceSeparatedListFormatter : WhiteSpaceSeparatedListFormatter + { + internal SpaceSeparatedListFormatter(FormatterVisitor visitor, SqlCodeObject codeObject, bool incrementIndentLevelOnPrefixRegion) + : base(visitor, codeObject, incrementIndentLevelOnPrefixRegion) + { + } + + internal override string FormatWhitespace(string original, FormatContext context) + { + return FormatterUtilities.NormalizeNewLinesOrCondenseToOneSpace(original, context); + } + + } + + internal class NewLineSeparatedListFormatter : WhiteSpaceSeparatedListFormatter + { + public NewLineSeparatedListFormatter(FormatterVisitor visitor, SqlCodeObject codeObject, bool incrementIndentLevelOnPrefixRegion) + : base(visitor, codeObject, incrementIndentLevelOnPrefixRegion) + { + } + + internal override string FormatWhitespace(string original, FormatContext context) + { + return FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum(original, context); + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlBatchFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlBatchFormatter.cs new file mode 100644 index 00000000..93404982 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlBatchFormatter.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Composition; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlBatchFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlBatch codeObject) + { + return new SqlBatchFormatter(visitor, codeObject); + } + } + + class SqlBatchFormatter : NewLineSeparatedListFormatter + { + public SqlBatchFormatter(FormatterVisitor visitor, SqlCodeObject codeObject) + :base(visitor, codeObject, false) + { + } + + internal override void ProcessPrefixRegion(int startTokenNumber, int firstChildStartTokenNumber) + { + for (int i = startTokenNumber; i < firstChildStartTokenNumber; i++) + { + SimpleProcessToken(i, FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum); + } + } + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlBinaryBooleanExpressionFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlBinaryBooleanExpressionFormatter.cs new file mode 100644 index 00000000..d6258352 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlBinaryBooleanExpressionFormatter.cs @@ -0,0 +1,58 @@ +// +// 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.Composition; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlBinaryBooleanExpressionFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlBinaryBooleanExpression codeObject) + { + return new SqlBinaryBooleanExpressionFormatter(visitor, codeObject); + } + } + + internal class SqlBinaryBooleanExpressionFormatter : ASTNodeFormatterT + { + SpaceSeparatedListFormatter SpaceSeparatedListFormatter { get; set; } + + internal SqlBinaryBooleanExpressionFormatter(FormatterVisitor visitor, SqlBinaryBooleanExpression codeObject) + : base(visitor, codeObject) + { + SpaceSeparatedListFormatter = new SpaceSeparatedListFormatter(visitor, codeObject, true); + } + + internal override void ProcessChild(SqlCodeObject child) + { + Validate.IsNotNull(nameof(child), child); + SpaceSeparatedListFormatter.ProcessChild(child); + } + + internal override void ProcessPrefixRegion(int startTokenNumber, int firstChildStartTokenNumber) + { + SpaceSeparatedListFormatter.ProcessPrefixRegion(startTokenNumber, firstChildStartTokenNumber); + } + + internal override void ProcessSuffixRegion(int lastChildEndTokenNumber, int endTokenNumber) + { + SpaceSeparatedListFormatter.ProcessSuffixRegion(lastChildEndTokenNumber, endTokenNumber); + } + + internal override void ProcessInterChildRegion(SqlCodeObject previousChild, SqlCodeObject nextChild) + { + Validate.IsNotNull(nameof(previousChild), previousChild); + Validate.IsNotNull(nameof(nextChild), nextChild); + + SpaceSeparatedListFormatter.ProcessInterChildRegion(previousChild, nextChild); + } + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlBinaryQueryExpressionFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlBinaryQueryExpressionFormatter.cs new file mode 100644 index 00000000..f48c6f5e --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlBinaryQueryExpressionFormatter.cs @@ -0,0 +1,216 @@ +// +// 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.Composition; +using System.Diagnostics; +using Babel.ParserGenerator; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlBinaryQueryExpressionFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlBinaryQueryExpression codeObject) + { + return new SqlBinaryQueryExpressionFormatter(visitor, codeObject); + } + } + + class SqlBinaryQueryExpressionFormatter : ASTNodeFormatterT + { + + internal SqlBinaryQueryExpressionFormatter(FormatterVisitor visitor, SqlBinaryQueryExpression codeObject) + : base(visitor, codeObject) + { + } + + internal override void ProcessPrefixRegion(int startTokenNumber, int firstChildStartTokenNumber) + { + if (CodeObject.Left is SqlQuerySpecification) + { + IncrementIndentLevel(); + } + + // if the start token is not a whitespace, we need to insert the indent string + TokenData td = GetTokenData(startTokenNumber); + if (!IsTokenWhitespace(td)) + { + string newWhiteSpace = GetIndentString(); + AddReplacement(new Replacement(td.StartIndex, string.Empty, newWhiteSpace)); + } + + for (int i = startTokenNumber; i < firstChildStartTokenNumber; i++) + { + SimpleProcessToken(i, FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum); + } + + if (firstChildStartTokenNumber - 1 >= startTokenNumber) + { + IndentChild(firstChildStartTokenNumber); + } + } + + private void IndentChild(int firstChildStartTokenNumber) + { + string newWhiteSpace = GetIndentString(); + + if (!IsTokenWhitespace(PreviousTokenData(firstChildStartTokenNumber))) + { + newWhiteSpace = Environment.NewLine + newWhiteSpace; + } + + TokenData td = GetTokenData(firstChildStartTokenNumber); + AddReplacement(td.StartIndex, string.Empty, newWhiteSpace); + } + + internal override void ProcessInterChildRegion(SqlCodeObject previousChild, SqlCodeObject nextChild) + { + Validate.IsNotNull(nameof(previousChild), previousChild); + Validate.IsNotNull(nameof(nextChild), nextChild); + + // find the potition of the operator token: + // operatorTokenNumber + #region FindOperator + + // look for the expression type based on the operator and determine its type and position + int binaryOperatorTokenID = FormatterTokens.LAST_TOKEN; + bool foundOperator = false; + int operatorTokenNumber = nextChild.Position.startTokenNumber; + for (int i = previousChild.Position.endTokenNumber; !foundOperator && i < nextChild.Position.startTokenNumber; i++) + { + TokenData td = GetTokenData(i); + if ( td.TokenId == FormatterTokens.TOKEN_UNION || + td.TokenId == FormatterTokens.TOKEN_INTERSECT || + td.TokenId == FormatterTokens.TOKEN_EXCEPT ) + { + foundOperator = true; + binaryOperatorTokenID = td.TokenId; + operatorTokenNumber = i; + } + } + + // check that we actually found one + Debug.Assert(foundOperator); + // if we found the operator, it means we also know its position number. + Debug.Assert(operatorTokenNumber >= previousChild.Position.endTokenNumber); + Debug.Assert(operatorTokenNumber < nextChild.Position.startTokenNumber); + // and we know its type + Debug.Assert( + binaryOperatorTokenID == FormatterTokens.TOKEN_UNION || + binaryOperatorTokenID == FormatterTokens.TOKEN_INTERSECT || + binaryOperatorTokenID == FormatterTokens.TOKEN_EXCEPT); + #endregion + + // process the tokens before the operator: + // [lastChild.Position.endTokenNumber, operatorTokenNumber) + #region BeforeOperator + + // If the first token is not a whitespace and it, we need to insert a newline in front + TokenData endTokenData = GetTokenData(previousChild.Position.endTokenNumber); + if (!IsTokenWhitespace(endTokenData)) + { + AddIndentedNewLineReplacement(endTokenData.StartIndex); + } + + for (int i = previousChild.Position.endTokenNumber; i < operatorTokenNumber - 1; i++) + { + SimpleProcessToken(i, FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum); + } + + if (CodeObject.Left is SqlQuerySpecification) + { + DecrementIndentLevel(); + } + + if (operatorTokenNumber - 1 >= previousChild.Position.endTokenNumber) + { + SimpleProcessToken(operatorTokenNumber - 1, FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum); + if (!IsTokenWhitespace(PreviousTokenData(operatorTokenNumber))) + { + TokenData td = GetTokenData(operatorTokenNumber); + AddIndentedNewLineReplacement(td.StartIndex); + } + } + + #endregion // BeforeOperator + + // process the operator: + // [operatorTokenNumber, firstTokenAfterOperator) + #region Operator + + // since the operator might contain more than one token, we will keep track of its end + int firstTokenAfterOperator = operatorTokenNumber + 1; + + // find where the operator ends + if (binaryOperatorTokenID == FormatterTokens.TOKEN_UNION) + { + // the union operator may or may not be followed by the "ALL" modifier, so it might span over a number of tokens + bool foundModifier = false; + int modifierTokenNumber = nextChild.Position.startTokenNumber; + + for (int i = operatorTokenNumber; !foundModifier && i < nextChild.Position.startTokenNumber; i++) + { + if (GetTokenData(i).TokenId == FormatterTokens.TOKEN_ALL) + { + foundModifier = true; + modifierTokenNumber = i; + } + } + + if (foundModifier) + { + // leave everythong between "UNION" and "ALL" just as it was, but format the keywords + firstTokenAfterOperator = modifierTokenNumber + 1; + } + } + else + { + // only format the operator + firstTokenAfterOperator = operatorTokenNumber + 1; + } + + ProcessTokenRange(operatorTokenNumber, firstTokenAfterOperator); + + #endregion // Operator + + // process tokens after the operator: + // [firstTokenAfterOperator, nextChild.Position.startTokenNumber) + #region AfterOperator + + if (CodeObject.Right is SqlQuerySpecification) + { + IncrementIndentLevel(); + } + + // if the first token is not a whitespace, we need to insert a newline in front + if (!TokenManager.IsTokenWhitespace(TokenManager.TokenList[firstTokenAfterOperator].TokenId)) + { + TokenData td = GetTokenData(firstTokenAfterOperator); + AddIndentedNewLineReplacement(td.StartIndex); + } + + for (int i = firstTokenAfterOperator; i < nextChild.Position.startTokenNumber; i++) + { + SimpleProcessToken(i, FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum); + } + + #endregion // AfterOperator + + } + + internal override void ProcessSuffixRegion(int lastChildEndTokenNumber, int endTokenNumber) + { + if (CodeObject.Right is SqlQuerySpecification) + { + DecrementIndentLevel(); + } + base.ProcessSuffixRegion(lastChildEndTokenNumber, endTokenNumber); + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlColumnDefinitionFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlColumnDefinitionFormatter.cs new file mode 100644 index 00000000..3582bbd0 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlColumnDefinitionFormatter.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Composition; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlColumnDefinitionFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlColumnDefinition codeObject) + { + return new SqlColumnDefinitionFormatter(visitor, codeObject); + } + } + + internal class SqlColumnDefinitionFormatter : ASTNodeFormatterT + { + private PaddedSpaceSeparatedListFormatter SpaceSeparatedListFormatter { get; set; } + + internal SqlColumnDefinitionFormatter(FormatterVisitor visitor, SqlColumnDefinition codeObject) + : base(visitor, codeObject) + { + SpaceSeparatedListFormatter = new PaddedSpaceSeparatedListFormatter(visitor, codeObject, Visitor.Context.CurrentColumnSpacingFormatDefinitions, true); + } + + internal override void ProcessChild(SqlCodeObject child) + { + Validate.IsNotNull(nameof(child), child); + SpaceSeparatedListFormatter.ProcessChild(child); + } + + internal override void ProcessPrefixRegion(int startTokenNumber, int firstChildStartTokenNumber) + { + SpaceSeparatedListFormatter.ProcessPrefixRegion(startTokenNumber, firstChildStartTokenNumber); + } + + internal override void ProcessSuffixRegion(int lastChildEndTokenNumber, int endTokenNumber) + { + SpaceSeparatedListFormatter.ProcessSuffixRegion(lastChildEndTokenNumber, endTokenNumber); + } + + internal override void ProcessInterChildRegion(SqlCodeObject previousChild, SqlCodeObject nextChild) + { + Validate.IsNotNull(nameof(previousChild), previousChild); + Validate.IsNotNull(nameof(nextChild), nextChild); + + SpaceSeparatedListFormatter.ProcessInterChildRegion(previousChild, nextChild); + } + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlCommonTableExpressionFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlCommonTableExpressionFormatter.cs new file mode 100644 index 00000000..f05efb88 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlCommonTableExpressionFormatter.cs @@ -0,0 +1,107 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Collections.Generic; +using System.Composition; +using System.Diagnostics; +using Babel.ParserGenerator; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlCommonTableExpressionFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlCommonTableExpression codeObject) + { + return new SqlCommonTableExpressionFormatter(visitor, codeObject); + } + } + + internal class SqlCommonTableExpressionFormatter : SysCommentsFormatterBase + { + + public SqlCommonTableExpressionFormatter(FormatterVisitor visitor, SqlCommonTableExpression codeObject) + : base(visitor, codeObject) + { + } + + protected override bool ShouldPlaceEachElementOnNewLine() + { + return FormatOptions.PlaceEachReferenceOnNewLineInQueryStatements; + } + + internal override void ProcessPrefixRegion(int startTokenNumber, int firstChildStartTokenNumber) + { + IncrementIndentLevel(); + base.ProcessPrefixRegion(startTokenNumber, firstChildStartTokenNumber); + } + + internal override void ProcessSuffixRegion(int lastChildEndTokenNumber, int endTokenNumber) + { + DecrementIndentLevel(); + base.ProcessSuffixRegion(lastChildEndTokenNumber, endTokenNumber); + } + + public override void Format() + { + int nextToken = ProcessExpressionName(CodeObject.Position.startTokenNumber); + + nextToken = ProcessColumns(nextToken); + + // TODO: should we indent the AS statement and then decrement indent at the end? + nextToken = ProcessAsToken(nextToken, indentAfterAs: false); + + nextToken = ProcessQueryExpression(nextToken); + + } + + private int ProcessQueryExpression(int nextToken) + { + NormalizeWhitespace normalizer = GetColumnWhitespaceNormalizer(); + nextToken = ProcessSectionInsideParentheses(nextToken, + normalizer: FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum, + isNewlineRequired: true, + processSection: (n) => ProcessQuerySection(n, CodeObject.QueryExpression)); + return nextToken; + } + + private int ProcessColumns(int nextToken) + { + if (CodeObject.ColumnList != null && CodeObject.ColumnList.Count > 0) + { + NormalizeWhitespace normalizer = GetColumnWhitespaceNormalizer(); + nextToken = ProcessSectionInsideParentheses(nextToken, normalizer, + isNewlineRequired: FormatOptions.PlaceEachReferenceOnNewLineInQueryStatements, + processSection: (n) => ProcessColumnList(n, CodeObject.ColumnList, normalizer)); + } + return nextToken; + } + + private NormalizeWhitespace GetColumnWhitespaceNormalizer() + { + if (FormatOptions.PlaceEachReferenceOnNewLineInQueryStatements) + { + return FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum; + } + return FormatterUtilities.NormalizeNewLinesOrCondenseToOneSpace; + } + + private int ProcessExpressionName(int nextToken) + { + SqlIdentifier name = CodeObject.Name; + for (int i = nextToken; i < name.Position.startTokenNumber; i++) + { + ProcessTokenEnsuringOneNewLineMinimum(i); + } + + ProcessTokenRange(name.Position.startTokenNumber, name.Position.endTokenNumber); + + nextToken = name.Position.endTokenNumber; + + return nextToken; + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlCompoundStatementFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlCompoundStatementFormatter.cs new file mode 100644 index 00000000..13fb9b25 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlCompoundStatementFormatter.cs @@ -0,0 +1,61 @@ +// +// 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.Composition; +using Babel.ParserGenerator; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlCompoundStatementFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlCompoundStatement codeObject) + { + return new SqlCompoundStatementFormatter(visitor, codeObject); + } + } + + class SqlCompoundStatementFormatter : NewLineSeparatedListFormatter + { + internal SqlCompoundStatementFormatter(FormatterVisitor visitor, SqlCompoundStatement codeObject) + : base(visitor, codeObject, true) + { + } + + internal override void ProcessPrefixRegion(int startTokenNumber, int firstChildStartTokenNumber) + { + + for (int i = startTokenNumber; i < firstChildStartTokenNumber; i++) + { + if (TokenManager.TokenList[i].TokenId == FormatterTokens.TOKEN_BEGIN_CS) + { + IncrementIndentLevel(); + } + SimpleProcessToken(i, FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum); + } + } + + internal override void ProcessSuffixRegion(int lastChildEndTokenNumber, int endTokenNumber) + { + DecrementIndentLevel(); + + for (int i = lastChildEndTokenNumber; i < endTokenNumber; i++) + { + if (TokenManager.TokenList[i].TokenId == FormatterTokens.TOKEN_END_CS + && !TokenManager.IsTokenWhitespace(TokenManager.TokenList[i-1].TokenId)) + { + TokenData td = TokenManager.TokenList[i]; + AddIndentedNewLineReplacement(td.StartIndex); + } + SimpleProcessToken(i, FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum); + } + } + + } + +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlCreateProcedureStatementFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlCreateProcedureStatementFormatter.cs new file mode 100644 index 00000000..a6ec2465 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlCreateProcedureStatementFormatter.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Composition; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlCreateProcedureStatementFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlCreateProcedureStatement codeObject) + { + return new SqlCreateProcedureStatementFormatter(visitor, codeObject); + } + } + + class SqlCreateProcedureStatementFormatter : NewLineSeparatedListFormatter + { + internal SqlCreateProcedureStatementFormatter(FormatterVisitor visitor, SqlCreateProcedureStatement codeObject) + : base(visitor, codeObject, false) + { + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlCreateTableStatementFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlCreateTableStatementFormatter.cs new file mode 100644 index 00000000..326476c7 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlCreateTableStatementFormatter.cs @@ -0,0 +1,195 @@ +// +// 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.Composition; +using System.Diagnostics; +using Babel.ParserGenerator; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlCreateTableStatementFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlCreateTableStatement codeObject) + { + return new SqlCreateTableStatementFormatter(visitor, codeObject); + } + } + + internal class SqlCreateTableStatementFormatter : ASTNodeFormatterT + { + internal SqlCreateTableStatementFormatter(FormatterVisitor visitor, SqlCreateTableStatement codeObject) + : base(visitor, codeObject) + { } + + internal override void ProcessPrefixRegion(int startTokenNumber, int firstChildStartTokenNumber) + { + int nTokens = firstChildStartTokenNumber - startTokenNumber; + Debug.Assert(nTokens >= 4, "unexpected token count for SqlCreateTableStatement prefix region"); + + int createTokenIndex = -1; + int tableTokenIndex = -1; + bool foundComment = false; + for (int i = startTokenNumber; i < firstChildStartTokenNumber; i++) + { + TokenData td = TokenManager.TokenList[i]; + + if (td.TokenId == FormatterTokens.LEX_END_OF_LINE_COMMENT) + { + foundComment = true; + } else if (td.TokenId == FormatterTokens.TOKEN_TABLE) + { + tableTokenIndex = i; + } else if (td.TokenId == FormatterTokens.TOKEN_CREATE) + { + createTokenIndex = i; + } + } + + // logic below doesn't support single-line comments inside of a create table statement + if (!foundComment && createTokenIndex < tableTokenIndex) + { + for (int i = startTokenNumber; i < firstChildStartTokenNumber; i++) + { + SimpleProcessToken(i, FormatterUtilities.NormalizeToOneSpace); + } + } + else + { + ProcessTokenRange(startTokenNumber, firstChildStartTokenNumber); + } + } + + internal override void ProcessSuffixRegion(int lastChildEndTokenNumber, int endTokenNumber) + { + // Due to a current limitation of the parser, the suffix region stops right after the list of column definitions. + // According to the TSQL grammar schema, the statement could continue (see: http://msdn.microsoft.com/en-us/library/ms174979.aspx). + // We will preserve the text in the sufix region in its current formatting, with the exception that we ensure the closed parenthesis + // which closes the list of column definitions is on a new line and that all the tokens preceding it (which should only be comments) + // are also each on a separate line and indented + + IncrementIndentLevel(); + + int closeParenToken = -1; + + for (int i = lastChildEndTokenNumber; i < endTokenNumber && closeParenToken < 0; i++) + { + if (TokenManager.TokenList[i].TokenId == 41) closeParenToken = i; + } + + if (closeParenToken > 0) + { + for (int i = lastChildEndTokenNumber; i < closeParenToken - 1; i++) + { + SimpleProcessToken(i, FormatterUtilities.NormalizeNewLinesInWhitespace); + } + + DecrementIndentLevel(); + + TokenData td2 = TokenManager.TokenList[closeParenToken - 1]; + + if (TokenManager.IsTokenWhitespace(td2.TokenId)) + { + SimpleProcessToken(closeParenToken - 1, FormatterUtilities.NormalizeNewLinesInWhitespace); + } + else + { + TokenData td = TokenManager.TokenList[closeParenToken]; + AddIndentedNewLineReplacement(td.StartIndex); + } + + // Add the closed parenthesis and the additional unparsed elements of the statement + // which should keep their old formatting + ProcessTokenRange(closeParenToken, endTokenNumber); + } + else + { + ProcessTokenRange(lastChildEndTokenNumber, endTokenNumber); + } + } + + internal override void ProcessInterChildRegion(SqlCodeObject previousChild, SqlCodeObject nextChild) + { + Validate.IsNotNull(nameof(previousChild), previousChild); + Validate.IsNotNull(nameof(nextChild), nextChild); + + if (previousChild is SqlObjectIdentifier && nextChild is SqlTableDefinition) + { + // + // We want to make sure that the open-paren is on a new line and followed by a new-line & correctly indented. + // + + // find the open paren token + int openParenToken = -1; + for (int i = previousChild.Position.endTokenNumber; i < nextChild.Position.startTokenNumber; i++) + { + TokenData currentToken = TokenManager.TokenList[i]; + if (currentToken.TokenId == 40) + { + openParenToken = i; + break; + } + } + + + + // normalize whitespace between last token & open paren. Each whitespace token should be condensed down to a single space character + for (int i = previousChild.Position.endTokenNumber; i < openParenToken - 1; i++) + { + SimpleProcessToken(i, FormatterUtilities.NormalizeToOneSpace); + } + + // If there is a whitespace before the open parenthisis, normalize it to a new line + TokenData td = TokenManager.TokenList[openParenToken - 1]; + + if (TokenManager.IsTokenWhitespace(td.TokenId)) + { + if (previousChild.Position.endTokenNumber < openParenToken) + { + SimpleProcessToken(openParenToken - 1, FormatterUtilities.NormalizeNewLinesInWhitespace); + } + } + else + { + if (previousChild.Position.endTokenNumber < openParenToken) + { + SimpleProcessToken(openParenToken - 1, FormatterUtilities.NormalizeToOneSpace); + } + TokenData tok = TokenManager.TokenList[openParenToken]; + AddIndentedNewLineReplacement(tok.StartIndex); + } + + // append open-paren token + ProcessTokenRange(openParenToken, openParenToken + 1); + + // process tokens between open paren & first child start + IncrementIndentLevel(); + for (int i = openParenToken + 1; i < nextChild.Position.startTokenNumber; i++) + { + SimpleProcessToken(i, FormatterUtilities.NormalizeNewLinesInWhitespace); + } + + // ensure we have at least one new line + if (openParenToken + 1 >= nextChild.Position.startTokenNumber || !TokenManager.IsTokenWhitespace(TokenManager.TokenList[nextChild.Position.startTokenNumber - 1].TokenId)) + { + TokenData tok = TokenManager.TokenList[nextChild.Position.startTokenNumber]; + AddIndentedNewLineReplacement(tok.StartIndex); + } + DecrementIndentLevel(); + + } + else + { + ProcessTokenRange(previousChild.Position.endTokenNumber, nextChild.Position.startTokenNumber); + } + + } + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlDataTypeFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlDataTypeFormatter.cs new file mode 100644 index 00000000..16797cc6 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlDataTypeFormatter.cs @@ -0,0 +1,61 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Composition; +using System.Diagnostics.CodeAnalysis; +using Babel.ParserGenerator; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlDataTypeFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlDataType codeObject) + { + return new SqlDataTypeFormatter(visitor, codeObject); + } + } + + internal class SqlDataTypeFormatter : ASTNodeFormatterT + { + internal SqlDataTypeFormatter(FormatterVisitor visitor, SqlDataType codeObject) + : base(visitor, codeObject) + { + } + + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")] + public override void Format() + { + int startTokenIndex = CodeObject.Position.startTokenNumber; + int endTokenIndex = CodeObject.Position.endTokenNumber; + + if (startTokenIndex == endTokenIndex - 1 && + CodeObject.TokenManager.TokenList[startTokenIndex].TokenId == FormatterTokens.TOKEN_ID) + { + string sql = GetTokenRangeAsOriginalString(startTokenIndex, startTokenIndex + 1); + if (FormatOptions.UppercaseDataTypes) + { + TokenData tok = TokenManager.TokenList[startTokenIndex]; + AddReplacement(tok.StartIndex, sql, sql.ToUpperInvariant()); + sql = sql.ToUpperInvariant(); + } + else if (FormatOptions.LowercaseDataTypes) + { + TokenData tok = TokenManager.TokenList[startTokenIndex]; + AddReplacement(tok.StartIndex, sql, sql.ToLowerInvariant()); + sql = sql.ToLowerInvariant(); + } + + } + else + { + base.Format(); + } + } + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlFromClauseFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlFromClauseFormatter.cs new file mode 100644 index 00000000..0a33dfcb --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlFromClauseFormatter.cs @@ -0,0 +1,57 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Composition; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlFromClauseFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlFromClause codeObject) + { + return new SqlFromClauseFormatter(visitor, codeObject); + } + } + + internal class SqlFromClauseFormatter : ASTNodeFormatterT + { + private CommaSeparatedListFormatter CommaSeparatedListFormatter { get; set; } + + internal SqlFromClauseFormatter(FormatterVisitor visitor, SqlFromClause codeObject) + : base(visitor, codeObject) + { + CommaSeparatedListFormatter = new CommaSeparatedListFormatter(visitor, codeObject, FormatOptions.PlaceEachReferenceOnNewLineInQueryStatements); + } + + internal override void ProcessChild(SqlCodeObject child) + { + Validate.IsNotNull(nameof(child), child); + CommaSeparatedListFormatter.ProcessChild(child); + } + + internal override void ProcessPrefixRegion(int startTokenNumber, int firstChildStartTokenNumber) + { + CommaSeparatedListFormatter.ProcessPrefixRegion(startTokenNumber, firstChildStartTokenNumber); + } + + internal override void ProcessSuffixRegion(int lastChildEndTokenNumber, int endTokenNumber) + { + CommaSeparatedListFormatter.ProcessSuffixRegion(lastChildEndTokenNumber, endTokenNumber); + } + + internal override void ProcessInterChildRegion(SqlCodeObject previousChild, SqlCodeObject nextChild) + { + Validate.IsNotNull(nameof(previousChild), previousChild); + Validate.IsNotNull(nameof(nextChild), nextChild); + + CommaSeparatedListFormatter.ProcessInterChildRegion(previousChild, nextChild); + } + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlInsertSpecificationFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlInsertSpecificationFormatter.cs new file mode 100644 index 00000000..d75d8078 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlInsertSpecificationFormatter.cs @@ -0,0 +1,205 @@ +// +// 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.Collections.Generic; +using System.Composition; +using System.Diagnostics; +using System.Globalization; +using Babel.ParserGenerator; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlInsertSpecificationFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlInsertSpecification codeObject) + { + return new SqlInsertSpecificationFormatter(visitor, codeObject); + } + } + + class SqlInsertSpecificationFormatter : ASTNodeFormatterT + { + internal SqlInsertSpecificationFormatter(FormatterVisitor visitor, SqlInsertSpecification codeObject) + : base(visitor, codeObject) + { + } + + public override void Format() + { + + IEnumerator firstChildEnum = CodeObject.Children.GetEnumerator(); + if (firstChildEnum.MoveNext()) + { + // + // format the text from the start of the object to the start of it's first child + // + ProcessPrefixRegion(CodeObject.Position.startTokenNumber, firstChildEnum.Current.Position.startTokenNumber); + int nextToken = firstChildEnum.Current.Position.startTokenNumber; + + // handle top specification + nextToken = ProcessTopSpecification(nextToken); + + // handle target + nextToken = ProcessTarget(nextToken); + + // handle target columns + nextToken = ProcessColumns(nextToken); + + // handle output clause + nextToken = ProcessOutputClause(nextToken); + + // handle values / derived table / execute statement / dml_table_source + nextToken = ProcessValues(nextToken); + } + else + { + throw new FormatFailedException("Insert statement has no children."); + } + } + + + internal int ProcessTopSpecification(int nextToken) + { + if (CodeObject.TopSpecification != null) + { + ProcessAndNormalizeWhitespaceRange(nextToken, CodeObject.TopSpecification.Position.startTokenNumber, + FormatterUtilities.NormalizeNewLinesOrCondenseToOneSpace); + + ProcessChild(CodeObject.TopSpecification); + + nextToken = CodeObject.TopSpecification.Position.endTokenNumber; + } + + return nextToken; + } + + + private int ProcessTarget(int nextToken) + { + Debug.Assert(CodeObject.Target != null, "No target in insert statement."); + + // find out if there is an "INTO" token + int intoTokenIndexOrTargetStartTokenIndex = CodeObject.Target.Position.startTokenNumber; + for (int i = nextToken; i < CodeObject.Target.Position.startTokenNumber; i++) + { + if (TokenManager.TokenList[i].TokenId == FormatterTokens.TOKEN_INTO) + { + intoTokenIndexOrTargetStartTokenIndex = i; + } + } + + // Process up to the INTO or Target index. If INTO isn't there, expect all whitespace tokens + ProcessAndNormalizeWhitespaceRange(nextToken, intoTokenIndexOrTargetStartTokenIndex, + FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum); + + IncrementIndentLevel(); + + // Process the INTO token and all whitespace up to the target start + ProcessAndNormalizeTokenRange(intoTokenIndexOrTargetStartTokenIndex, CodeObject.Target.Position.startTokenNumber, + FormatterUtilities.NormalizeNewLinesOrCondenseToOneSpace, areAllTokensWhitespace: false); + + ProcessChild(CodeObject.Target); + + nextToken = CodeObject.Target.Position.endTokenNumber; + DecrementIndentLevel(); + + return nextToken; + } + + private int ProcessColumns(int nextToken) + { + if (CodeObject.TargetColumns != null) + { + if (CodeObject.TargetColumns.Count > 0) + { + IncrementIndentLevel(); + + // if the next token is not a whitespace, a newline is enforced. + TokenData nextTokenData = GetTokenData(nextToken); + if (!IsTokenWhitespace(nextTokenData)) + { + AddIndentedNewLineReplacement(nextTokenData.StartIndex); + } + + NormalizeWhitespace f = FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum; + + // process tokens until we reach the closed parenthesis (with id 41) + for (int id = TokenManager.TokenList[nextToken].TokenId; id != 41; id = TokenManager.TokenList[++nextToken].TokenId) + { + SimpleProcessToken(nextToken, f); + if (id == 40) // open parenthesis (id == 40) changes the formatting + { + f = FormatterUtilities.NormalizeNewLinesOrCondenseToOneSpace; + } + } + + // process the cosed paren + SimpleProcessToken(nextToken, f); + + nextToken++; + + DecrementIndentLevel(); + } + } + + return nextToken; + } + + + private int ProcessValues(int nextToken) + { + if (CodeObject.Source != null && HasToken(nextToken)) + { + TokenData nextTokenData = GetTokenData(nextToken); + // if the next token is not a whitespace, a newline is enforced. + if (!IsTokenWhitespace(nextTokenData)) + { + AddIndentedNewLineReplacement(nextTokenData.StartIndex); + } + + ProcessAndNormalizeWhitespaceRange(nextToken, CodeObject.Source.Position.startTokenNumber, + FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum); + + ProcessChild(CodeObject.Source); + } + + return nextToken; + } + + private int ProcessOutputClause(int nextToken) + { + if (CodeObject.OutputIntoClause != null) + { + if (nextToken == CodeObject.OutputIntoClause.Position.startTokenNumber) + { + AddIndentedNewLineReplacement(GetTokenData(nextToken).StartIndex); + } + else + { + while (nextToken < CodeObject.OutputIntoClause.Position.startTokenNumber) + { + DebugAssertTokenIsWhitespaceOrComment(GetTokenData(nextToken), nextToken); + SimpleProcessToken(nextToken, FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum); + nextToken++; + } + + TokenData previousTokenData = PreviousTokenData(nextToken); + if (!IsTokenWhitespace(previousTokenData)) + { + AddIndentedNewLineReplacement(previousTokenData.StartIndex); + } + } + ProcessChild(CodeObject.OutputIntoClause); + nextToken = CodeObject.OutputIntoClause.Position.endTokenNumber; + } + + return nextToken; + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlInsertStatementFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlInsertStatementFormatter.cs new file mode 100644 index 00000000..f2f2ccb7 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlInsertStatementFormatter.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Composition; +using System.Diagnostics.CodeAnalysis; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlInsertStatementFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlInsertStatement codeObject) + { + return new SqlInsertStatementFormatter(visitor, codeObject); + } + } + + internal class SqlInsertStatementFormatter : NewLineSeparatedListFormatter + { + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + public SqlInsertStatementFormatter(FormatterVisitor visitor, SqlInsertStatement codeObject) + : base(visitor, codeObject, false) + { + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlProcedureDefinitionFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlProcedureDefinitionFormatter.cs new file mode 100644 index 00000000..1291807c --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlProcedureDefinitionFormatter.cs @@ -0,0 +1,91 @@ +// +// 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.Composition; +using Babel.ParserGenerator; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlProcedureDefinitionFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlProcedureDefinition codeObject) + { + return new SqlProcedureDefinitionFormatter(visitor, codeObject); + } + } + + class SqlProcedureDefinitionFormatter : CommaSeparatedListFormatter + { + NewLineSeparatedListFormatter NewLineSeparatedListFormatter { get; set; } + bool foundTokenWith; + + internal SqlProcedureDefinitionFormatter(FormatterVisitor visitor, SqlProcedureDefinition codeObject) + : base(visitor, codeObject, true) + { + NewLineSeparatedListFormatter = new NewLineSeparatedListFormatter(visitor, codeObject, false); + foundTokenWith = false; + } + + internal override void ProcessInterChildRegion(SqlCodeObject previousChild, SqlCodeObject nextChild) + { + Validate.IsNotNull(nameof(previousChild), previousChild); + Validate.IsNotNull(nameof(nextChild), nextChild); + + if (nextChild is SqlModuleOption) + { + if (!foundTokenWith) + { + DecrementIndentLevel(); + } + for (int i = previousChild.Position.endTokenNumber; i < nextChild.Position.startTokenNumber; i++) + { + if (!foundTokenWith && TokenManager.TokenList[i].TokenId == FormatterTokens.TOKEN_WITH) + { + IncrementIndentLevel(); + foundTokenWith = true; + } + SimpleProcessToken(i, FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum); + } + } + else if (previousChild is SqlObjectIdentifier) + { + NewLineSeparatedListFormatter.ProcessInterChildRegion(previousChild, nextChild); + } + else + { + base.ProcessInterChildRegion(previousChild, nextChild); + } + } + + internal override void ProcessSuffixRegion(int lastChildEndTokenNumber, int endTokenNumber) + { + DecrementIndentLevel(); + NormalizeWhitespace f = FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum; + for (int i = lastChildEndTokenNumber; i < endTokenNumber; i++) + { + if (TokenManager.TokenList[i].TokenId == FormatterTokens.TOKEN_AS + && !TokenManager.IsTokenWhitespace(TokenManager.TokenList[i-1].TokenId)) + { + TokenData td = TokenManager.TokenList[i]; + AddIndentedNewLineReplacement(td.StartIndex); + } + if (TokenManager.TokenList[i].TokenId == FormatterTokens.TOKEN_FOR) + { + f = FormatterUtilities.NormalizeNewLinesOrCondenseToOneSpace; + } + else if (TokenManager.TokenList[i].TokenId == FormatterTokens.TOKEN_REPLICATION) + { + f = FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum; + } + SimpleProcessToken(i, f); + } + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlQualifiedJoinTableExpressionFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlQualifiedJoinTableExpressionFormatter.cs new file mode 100644 index 00000000..150c3150 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlQualifiedJoinTableExpressionFormatter.cs @@ -0,0 +1,57 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Composition; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlQualifiedJoinTableExpressionFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlQualifiedJoinTableExpression codeObject) + { + return new SqlQualifiedJoinTableExpressionFormatter(visitor, codeObject); + } + } + + internal class SqlQualifiedJoinTableExpressionFormatter : ASTNodeFormatterT + { + SpaceSeparatedListFormatter SpaceSeparatedListFormatter { get; set; } + + internal SqlQualifiedJoinTableExpressionFormatter(FormatterVisitor visitor, SqlQualifiedJoinTableExpression codeObject) + : base(visitor, codeObject) + { + SpaceSeparatedListFormatter = new SpaceSeparatedListFormatter(visitor, codeObject, false); + } + + internal override void ProcessChild(SqlCodeObject child) + { + Validate.IsNotNull(nameof(child), child); + SpaceSeparatedListFormatter.ProcessChild(child); + } + + internal override void ProcessPrefixRegion(int startTokenNumber, int firstChildStartTokenNumber) + { + SpaceSeparatedListFormatter.ProcessPrefixRegion(startTokenNumber, firstChildStartTokenNumber); + } + + internal override void ProcessSuffixRegion(int lastChildEndTokenNumber, int endTokenNumber) + { + SpaceSeparatedListFormatter.ProcessSuffixRegion(lastChildEndTokenNumber, endTokenNumber); + } + + internal override void ProcessInterChildRegion(SqlCodeObject previousChild, SqlCodeObject nextChild) + { + Validate.IsNotNull(nameof(previousChild), previousChild); + Validate.IsNotNull(nameof(nextChild), nextChild); + + SpaceSeparatedListFormatter.ProcessInterChildRegion(previousChild, nextChild); + } + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlQuerySpecificationFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlQuerySpecificationFormatter.cs new file mode 100644 index 00000000..c90993f7 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlQuerySpecificationFormatter.cs @@ -0,0 +1,57 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Composition; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlQuerySpecificationFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlQuerySpecification codeObject) + { + return new SqlQuerySpecificationFormatter(visitor, codeObject); + } + } + + internal class SqlQuerySpecificationFormatter : ASTNodeFormatterT + { + WhiteSpaceSeparatedListFormatter WhiteSpaceSeparatedListFormatter { get; set; } + + internal SqlQuerySpecificationFormatter(FormatterVisitor visitor, SqlQuerySpecification codeObject) + : base(visitor, codeObject) + { + WhiteSpaceSeparatedListFormatter = new NewLineSeparatedListFormatter(visitor, codeObject, false); + } + + internal override void ProcessChild(SqlCodeObject child) + { + Validate.IsNotNull(nameof(child), child); + WhiteSpaceSeparatedListFormatter.ProcessChild(child); + } + + internal override void ProcessPrefixRegion(int startTokenNumber, int firstChildStartTokenNumber) + { + WhiteSpaceSeparatedListFormatter.ProcessPrefixRegion(startTokenNumber, firstChildStartTokenNumber); + } + + internal override void ProcessSuffixRegion(int lastChildEndTokenNumber, int endTokenNumber) + { + WhiteSpaceSeparatedListFormatter.ProcessSuffixRegion(lastChildEndTokenNumber, endTokenNumber); + } + + internal override void ProcessInterChildRegion(SqlCodeObject previousChild, SqlCodeObject nextChild) + { + Validate.IsNotNull(nameof(previousChild), previousChild); + Validate.IsNotNull(nameof(nextChild), nextChild); + + WhiteSpaceSeparatedListFormatter.ProcessInterChildRegion(previousChild, nextChild); + } + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlQueryWithClauseFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlQueryWithClauseFormatter.cs new file mode 100644 index 00000000..389791ae --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlQueryWithClauseFormatter.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Composition; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlQueryWithClauseFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlQueryWithClause codeObject) + { + return new SqlQueryWithClauseFormatter(visitor, codeObject); + } + } + + class SqlQueryWithClauseFormatter : CommaSeparatedListFormatter + { + public SqlQueryWithClauseFormatter(FormatterVisitor visitor, SqlQueryWithClause codeObject) + : base(visitor, codeObject, true) + { + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlRowConstructorExpressionFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlRowConstructorExpressionFormatter.cs new file mode 100644 index 00000000..892082d4 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlRowConstructorExpressionFormatter.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Composition; +using System.Diagnostics.CodeAnalysis; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlRowConstructorExpressionFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlRowConstructorExpression codeObject) + { + return new SqlRowConstructorExpressionFormatter(visitor, codeObject); + } + } + + internal class SqlRowConstructorExpressionFormatter : CommaSeparatedListFormatter + { + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + public SqlRowConstructorExpressionFormatter(FormatterVisitor visitor, SqlRowConstructorExpression codeObject) + : base(visitor, codeObject, false) + { + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlSelectClauseFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlSelectClauseFormatter.cs new file mode 100644 index 00000000..17ab0ed3 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlSelectClauseFormatter.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Composition; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlSelectClauseFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlSelectClause codeObject) + { + return new SqlSelectClauseFormatter(visitor, codeObject); + } + } + + internal class SqlSelectClauseFormatter : CommaSeparatedListFormatter + { + private NewLineSeparatedListFormatter NewLineSeparatedListFormatter { get; set; } + + internal SqlSelectClauseFormatter(FormatterVisitor visitor, SqlSelectClause codeObject) + : base(visitor, codeObject, visitor.Context.FormatOptions.PlaceEachReferenceOnNewLineInQueryStatements) + { + NewLineSeparatedListFormatter = new NewLineSeparatedListFormatter(visitor, codeObject, true); + } + + internal override void ProcessInterChildRegion(SqlCodeObject previousChild, SqlCodeObject nextChild) + { + Validate.IsNotNull(nameof(previousChild), previousChild); + Validate.IsNotNull(nameof(nextChild), nextChild); + + if (previousChild is SqlTopSpecification) + { + NewLineSeparatedListFormatter.ProcessInterChildRegion(previousChild, nextChild); + } + else + { + base.ProcessInterChildRegion(previousChild, nextChild); + } + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlSelectSpecificationFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlSelectSpecificationFormatter.cs new file mode 100644 index 00000000..53574b5e --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlSelectSpecificationFormatter.cs @@ -0,0 +1,124 @@ +// +// 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.Composition; +using System.Diagnostics; +using System.Globalization; +using Babel.ParserGenerator; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlSelectSpecificationFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlSelectSpecification codeObject) + { + return new SqlSelectSpecificationFormatter(visitor, codeObject); + } + } + + internal class SqlSelectSpecificationFormatter : NewLineSeparatedListFormatter + { + + internal SqlSelectSpecificationFormatter(FormatterVisitor visitor, SqlSelectSpecification codeObject) + : base(visitor, codeObject, false) + { + + } + + internal override void ProcessChild(SqlCodeObject child) + { + Validate.IsNotNull(nameof(child), child); + base.ProcessChild(child); + if (child is SqlForBrowseClause) + { + DecrementIndentLevel(); + } + } + + internal override void ProcessSuffixRegion(int lastChildEndTokenNumber, int endTokenNumber) + { + for (int i = lastChildEndTokenNumber; i < endTokenNumber; i++) + { + SimpleProcessToken(i, FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum); + } + } + + internal override void ProcessInterChildRegion(SqlCodeObject previousChild, SqlCodeObject nextChild) + { + Validate.IsNotNull(nameof(previousChild), previousChild); + Validate.IsNotNull(nameof(nextChild), nextChild); + + /* Due to the current behavior of the T-SQL Parser, the FOR clause needs to be treated separately */ + if (nextChild is SqlForBrowseClause || nextChild is SqlForXmlClause) + { + #region Find the "FOR" token + int forTokenIndex = previousChild.Position.endTokenNumber; + TokenData td = TokenManager.TokenList[forTokenIndex]; + while (td.TokenId != FormatterTokens.TOKEN_FOR && forTokenIndex < CodeObject.Position.endTokenNumber) + { + Debug.Assert( + TokenManager.IsTokenComment(td.TokenId) + || TokenManager.IsTokenWhitespace(td.TokenId) + , string.Format(CultureInfo.CurrentCulture, "Unexpected token \"{0}\" before the FOR token.", Visitor.Context.GetTokenRangeAsOriginalString(forTokenIndex, forTokenIndex + 1)) + ); + ++forTokenIndex; + td = TokenManager.TokenList[forTokenIndex]; + } + Debug.Assert(forTokenIndex < CodeObject.Position.endTokenNumber, "No FOR token."); + #endregion // Find the "FOR" token + + + #region Process the tokens before the "FOR" token + for (int i = previousChild.Position.endTokenNumber; i < forTokenIndex; i++) + { + Debug.Assert( + TokenManager.IsTokenComment(TokenManager.TokenList[i].TokenId) + || TokenManager.IsTokenWhitespace(TokenManager.TokenList[i].TokenId) + , string.Format(CultureInfo.CurrentCulture, "Unexpected token \"{0}\" before the FOR token.", Visitor.Context.GetTokenRangeAsOriginalString(i, i + 1)) + ); + SimpleProcessToken(i, FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum); + } + #endregion // Process the tokens before the "FOR" token + + #region Process the "FOR" token + if (previousChild.Position.endTokenNumber >= forTokenIndex + || !TokenManager.IsTokenWhitespace(TokenManager.TokenList[forTokenIndex - 1].TokenId)) + { + td = TokenManager.TokenList[forTokenIndex]; + AddIndentedNewLineReplacement(td.StartIndex); + } + Visitor.Context.ProcessTokenRange(forTokenIndex, forTokenIndex + 1); + IncrementIndentLevel(); + + int nextToken = forTokenIndex + 1; + Debug.Assert(nextToken < CodeObject.Position.endTokenNumber, "View Definition ends unexpectedly after the FOR token."); + // Ensure a whitespace after the "FOR" token + if (!TokenManager.IsTokenWhitespace(TokenManager.TokenList[nextToken].TokenId)) + { + td = TokenManager.TokenList[forTokenIndex]; + AddIndentedNewLineReplacement(td.StartIndex); + } + #endregion // Process the "FOR" token + + #region Process tokens after the FOR token + for (int i = nextToken; i < nextChild.Position.startTokenNumber; i++) + { + SimpleProcessToken(i, FormatterUtilities.NormalizeNewLinesOrCondenseToOneSpace); + } + #endregion // Process tokens after the FOR token + } + else + { + base.ProcessInterChildRegion(previousChild, nextChild); + } + } + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlSelectStatementFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlSelectStatementFormatter.cs new file mode 100644 index 00000000..b3698ae3 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlSelectStatementFormatter.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Composition; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlSelectStatementFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlSelectStatement codeObject) + { + return new SqlSelectStatementFormatter(visitor, codeObject); + } + } + + class SqlSelectStatementFormatter : NewLineSeparatedListFormatter + { + + internal SqlSelectStatementFormatter(FormatterVisitor visitor, SqlSelectStatement codeObject) + : base(visitor, codeObject, false) + { + + } + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlTableConstructorExpressionFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlTableConstructorExpressionFormatter.cs new file mode 100644 index 00000000..70d5bd22 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlTableConstructorExpressionFormatter.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Composition; +using System.Diagnostics.CodeAnalysis; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlTableConstructorExpressionFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlTableConstructorExpression codeObject) + { + return new SqlTableConstructorExpressionFormatter(visitor, codeObject); + } + } + + internal class SqlTableConstructorExpressionFormatter : CommaSeparatedListFormatter + { + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + public SqlTableConstructorExpressionFormatter(FormatterVisitor visitor, SqlTableConstructorExpression codeObject) + : base(visitor, codeObject, true) + { + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlTableDefinitionFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlTableDefinitionFormatter.cs new file mode 100644 index 00000000..4b20e059 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlTableDefinitionFormatter.cs @@ -0,0 +1,99 @@ +// +// 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.Collections.Generic; +using System.Composition; +using System.Linq; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlTableDefinitionFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlTableDefinition codeObject) + { + return new SqlTableDefinitionFormatter(visitor, codeObject); + } + } + + [Export(typeof(ASTNodeFormatter))] + internal class SqlTableDefinitionFormatter : ASTNodeFormatterT + { + private CommaSeparatedListFormatter CommaSeparatedListFormatter { get; set; } + + public SqlTableDefinitionFormatter(FormatterVisitor visitor, SqlTableDefinition codeObject) + : base(visitor, codeObject) + { + CommaSeparatedListFormatter = new CommaSeparatedListFormatter(visitor, codeObject, true); + + // figure out the size of paddings required to align column definitions in a "columnar" form + if (FormatOptions.AlignColumnDefinitionsInColumns) + { + int range1MaxLength = 0; + int range2MaxLength = 0; + + foreach (SqlCodeObject child in CodeObject.Children) + { + if (child is SqlColumnDefinition && !(child is SqlComputedColumnDefinition)) + { + SqlIdentifier identifierChild = child.Children.ElementAtOrDefault(0) as SqlIdentifier; + + if (identifierChild == null) + { + throw new FormatFailedException("unexpected token at index start Token Index"); + } + + string s1 = child.TokenManager.GetText(identifierChild.Position.startTokenNumber, identifierChild.Position.endTokenNumber); + range1MaxLength = Math.Max(range1MaxLength, s1.Length); + + SqlDataTypeSpecification dataTypeChildchild = child.Children.ElementAtOrDefault(1) as SqlDataTypeSpecification; + + // token "timestamp" should be ignorred + if (dataTypeChildchild != null) + { + string s2 = child.TokenManager.GetText(dataTypeChildchild.Position.startTokenNumber, dataTypeChildchild.Position.endTokenNumber); + range2MaxLength = Math.Max(range2MaxLength, s2.Length); + } + } + } + + PaddedSpaceSeparatedListFormatter.ColumnSpacingFormatDefinition d1 = new PaddedSpaceSeparatedListFormatter.ColumnSpacingFormatDefinition(typeof(SqlIdentifier), typeof(SqlDataTypeSpecification), range1MaxLength + 1); + PaddedSpaceSeparatedListFormatter.ColumnSpacingFormatDefinition d2 = new PaddedSpaceSeparatedListFormatter.ColumnSpacingFormatDefinition(typeof(SqlDataTypeSpecification), null, range2MaxLength + 1); + List columnSpacingFormatDefinitions = new List(2); + columnSpacingFormatDefinitions.Add(d1); + columnSpacingFormatDefinitions.Add(d2); + Visitor.Context.CurrentColumnSpacingFormatDefinitions = columnSpacingFormatDefinitions; + } + } + + internal override void ProcessChild(SqlCodeObject child) + { + Validate.IsNotNull(nameof(child), child); + CommaSeparatedListFormatter.ProcessChild(child); + } + + internal override void ProcessPrefixRegion(int startTokenNumber, int firstChildStartTokenNumber) + { + CommaSeparatedListFormatter.ProcessPrefixRegion(startTokenNumber, firstChildStartTokenNumber); + } + + internal override void ProcessSuffixRegion(int lastChildEndTokenNumber, int endTokenNumber) + { + Visitor.Context.CurrentColumnSpacingFormatDefinitions = null; + CommaSeparatedListFormatter.ProcessSuffixRegion(lastChildEndTokenNumber, endTokenNumber); + } + + internal override void ProcessInterChildRegion(SqlCodeObject previousChild, SqlCodeObject nextChild) + { + Validate.IsNotNull(nameof(previousChild), previousChild); + Validate.IsNotNull(nameof(nextChild), nextChild); + + CommaSeparatedListFormatter.ProcessInterChildRegion(previousChild, nextChild); + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlViewDefinitionFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlViewDefinitionFormatter.cs new file mode 100644 index 00000000..cd40334a --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SqlViewDefinitionFormatter.cs @@ -0,0 +1,151 @@ +// +// 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.Collections.Generic; +using System.Composition; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using Babel.ParserGenerator; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(ASTNodeFormatterFactory))] + internal class SqlViewDefinitionFormatterFactory : ASTNodeFormatterFactoryT + { + protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlViewDefinition codeObject) + { + return new SqlViewDefinitionFormatter(visitor, codeObject); + } + } + + class SqlViewDefinitionFormatter : SysCommentsFormatterBase + { + + internal SqlViewDefinitionFormatter(FormatterVisitor visitor, SqlViewDefinition sqlCodeObject) + : base(visitor, sqlCodeObject) + { + } + + protected override bool ShouldPlaceEachElementOnNewLine() + { + return true; + } + + public override void Format() + { + LexLocation loc = CodeObject.Position; + + SqlCodeObject firstChild = CodeObject.Children.FirstOrDefault(); + if (firstChild != null) + { + // + // format the text from the start of the object to the start of its first child + // + LexLocation firstChildStart = firstChild.Position; + ProcessPrefixRegion(loc.startTokenNumber, firstChildStart.startTokenNumber); + + ProcessChild(firstChild); + + // keep track of the next token to process + int nextToken = firstChildStart.endTokenNumber; + + // process the columns if available + nextToken = ProcessColumns(nextToken); + + // process options if available + nextToken = ProcessOptions(nextToken); + + // process the region containing the AS token + nextToken = ProcessAsToken(nextToken, indentAfterAs: true); + + // process the query with clause if present + nextToken = ProcessQueryWithClause(nextToken); + + // process the query expression + nextToken = ProcessQueryExpression(nextToken); + + DecrementIndentLevel(); + + // format text from end of last child to end of object. + SqlCodeObject lastChild = CodeObject.Children.LastOrDefault(); + Debug.Assert(lastChild != null, "last child is null. Need to write code to deal with this case"); + ProcessSuffixRegion(lastChild.Position.endTokenNumber, loc.endTokenNumber); + } + else + { + // no children + Visitor.Context.ProcessTokenRange(loc.startTokenNumber, loc.endTokenNumber); + } + } + + private int ProcessColumns(int nextToken) + { + if (CodeObject.ColumnList != null && CodeObject.ColumnList.Count > 0) + { + nextToken = ProcessSectionInsideParentheses(nextToken, FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum, + isNewlineRequired: true, + processSection: (n) => ProcessColumnList(n, CodeObject.ColumnList, FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum)); + } + return nextToken; + } + + private int ProcessOptions(int nextToken) + { + if (CodeObject.Options != null && CodeObject.Options.Count > 0) + { + int withTokenIndex = FindTokenWithId(nextToken, FormatterTokens.TOKEN_WITH); + + // Preprocess + ProcessTokenRangeEnsuringOneNewLineMinumum(nextToken, withTokenIndex); + + nextToken = ProcessWithStatementStart(nextToken, withTokenIndex); + + nextToken = ProcessOptionsSection(nextToken); + + DecrementIndentLevel(); + } + return nextToken; + } + + private int ProcessOptionsSection(int nextToken) + { + // find where the options start + IEnumerator optionEnum = CodeObject.Options.GetEnumerator(); + if (optionEnum.MoveNext()) + { + ProcessAndNormalizeWhitespaceRange(nextToken, optionEnum.Current.Position.startTokenNumber, + FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum); + + // Process options + ProcessChild(optionEnum.Current); + SqlModuleOption previousOption = optionEnum.Current; + while (optionEnum.MoveNext()) + { + CommaSeparatedList.ProcessInterChildRegion(previousOption, optionEnum.Current); + ProcessChild(optionEnum.Current); + previousOption = optionEnum.Current; + } + nextToken = previousOption.Position.endTokenNumber; + } + + return nextToken; + } + + private int ProcessQueryWithClause(int nextToken) + { + return ProcessQuerySection(nextToken, CodeObject.QueryWithClause); + } + + private int ProcessQueryExpression(int nextToken) + { + return ProcessQuerySection(nextToken, CodeObject.QueryExpression); + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SysCommentsFormatterBase.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SysCommentsFormatterBase.cs new file mode 100644 index 00000000..885702a9 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/SysCommentsFormatterBase.cs @@ -0,0 +1,210 @@ +// +// 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.Collections.Generic; +using System.Diagnostics; +using Babel.ParserGenerator; +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + /// + /// Common base class for objects dealing with sys comments. These follow + /// similar patterns so identical methods are held here + /// + internal abstract class SysCommentsFormatterBase : ASTNodeFormatterT + where T : SqlCodeObject + { + + internal CommaSeparatedListFormatter CommaSeparatedList { get; set; } + + public SysCommentsFormatterBase(FormatterVisitor visitor, T codeObject) + : base(visitor, codeObject) + { + CommaSeparatedList = new CommaSeparatedListFormatter(Visitor, CodeObject, ShouldPlaceEachElementOnNewLine()); + } + + protected abstract bool ShouldPlaceEachElementOnNewLine(); + + protected void ProcessTokenEnsuringOneNewLineMinimum(int tokenIndex) + { + ProcessTokenAndNormalize(tokenIndex, FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum); + } + + + /// + /// processes any section in a query, since the basic behavior is constant + /// + protected int ProcessQuerySection(int nextToken, SqlCodeObject queryObject) + { + if (queryObject != null) + { + ProcessAndNormalizeWhitespaceRange(nextToken, queryObject.Position.startTokenNumber, + FormatterUtilities.NormalizeNewLinesEnsureOneNewLineMinimum); + ProcessChild(queryObject); + nextToken = queryObject.Position.endTokenNumber; + } + return nextToken; + } + + protected int ProcessSectionInsideParentheses(int nextToken, NormalizeWhitespace normalizer, bool isNewlineRequired, + Func processSection) + { + int openParenIndex = FindOpenParenthesis(nextToken); + ProcessAndNormalizeWhitespaceRange(nextToken, openParenIndex, normalizer); + nextToken = ProcessOpenParenthesis(nextToken, openParenIndex, isNewlineRequired); + + nextToken = processSection(nextToken); + + int closedParenIndex = FindClosedParenthesis(nextToken); + + ProcessRegionBeforeClosedParenthesis(nextToken, closedParenIndex, normalizer, isNewlineRequired); + + // Process closed parenthesis + ProcessTokenRange(closedParenIndex, closedParenIndex + 1); + nextToken = closedParenIndex + 1; + + return nextToken; + } + + protected int FindOpenParenthesis(int openParenIndex) + { + return FindTokenWithId(openParenIndex, 40); + } + + protected int FindClosedParenthesis(int nextToken) + { + return FindTokenWithId(nextToken, 41); + } + + /// + /// if there was no whitespace before the parenthesis to be converted into a newline, + /// and the references need to be on a newline, then append a newline + /// + protected int ProcessOpenParenthesis(int nextToken, int openParenIndex, bool isNewlineRequired) + { + return ProcessCompoundStatementStart(ref nextToken, openParenIndex, isNewlineRequired); + } + + protected int ProcessWithStatementStart(int nextToken, int withTokenIndex) + { + return ProcessCompoundStatementStart(ref nextToken, withTokenIndex, true); + } + + protected int ProcessCompoundStatementStart(ref int nextToken, int compoundStartIndex, bool isNewlineRequired) + { + // if a newline is required and there was no whitespace before the start to be + // converted into a newline, then append a newline + if (isNewlineRequired + && (nextToken >= compoundStartIndex + || !IsTokenWhitespace(PreviousTokenData(compoundStartIndex)))) + { + // Note: nextToken index value does not match the Startindex of the TokenData. When adding + // indentation, always get the TokenData and its StartIndex value + TokenData td = GetTokenData(compoundStartIndex); + AddIndentedNewLineReplacement(td.StartIndex); + } + ProcessTokenRange(compoundStartIndex, compoundStartIndex + 1); + IncrementIndentLevel(); + + // Move our pointer past the start of the compount statement + nextToken = compoundStartIndex + 1; + TokenData nextTokenData = GetTokenData(nextToken); + + // Ensure a newline after the open parenthesis + if (isNewlineRequired + && !IsTokenWhitespace(nextTokenData)) + { + AddIndentedNewLineReplacement(nextTokenData.StartIndex); + } + return nextToken; + } + + protected void ProcessRegionBeforeClosedParenthesis(int startIndex, int closedParenIndex, NormalizeWhitespace normalizer, bool isNewlineRequired) + { + for (int i = startIndex; i < closedParenIndex - 1; i++) + { + ProcessTokenAndNormalize(i, normalizer); + } + DecrementIndentLevel(); + if (startIndex < closedParenIndex) + { + SimpleProcessToken(closedParenIndex - 1, normalizer); + } + + // Enforce a whitespace before the closing parenthesis + TokenData td = PreviousTokenData(closedParenIndex); + if (isNewlineRequired && !IsTokenWhitespace(td)) + { + td = GetTokenData(closedParenIndex); + AddIndentedNewLineReplacement(td.StartIndex); + } + } + + protected int ProcessColumnList(int nextToken, SqlIdentifierCollection columnList, NormalizeWhitespace normalizer) + { + // find where the columns start + IEnumerator columnEnum = columnList.GetEnumerator(); + if (columnEnum.MoveNext()) + { + ProcessAndNormalizeWhitespaceRange(nextToken, columnEnum.Current.Position.startTokenNumber, normalizer); + + ProcessChild(columnEnum.Current); + SqlIdentifier previousColumn = columnEnum.Current; + while (columnEnum.MoveNext()) + { + CommaSeparatedList.ProcessInterChildRegion(previousColumn, columnEnum.Current); + ProcessChild(columnEnum.Current); + previousColumn = columnEnum.Current; + } + nextToken = previousColumn.Position.endTokenNumber; + } + return nextToken; + } + + protected int ProcessAsToken(int nextToken, bool indentAfterAs) + { + int asTokenIndex = FindTokenWithId(nextToken, FormatterTokens.TOKEN_AS); + + // Preprocess + ProcessTokenRangeEnsuringOneNewLineMinumum(nextToken, asTokenIndex); + + // Process As + if (nextToken >= asTokenIndex + || !IsTokenWhitespace(PreviousTokenData(asTokenIndex))) + { + TokenData td = GetTokenData(asTokenIndex); + AddIndentedNewLineReplacement(td.StartIndex); + } + ProcessTokenRange(asTokenIndex, asTokenIndex + 1); + + if (indentAfterAs) + { + IncrementIndentLevel(); + } + // Post Process + nextToken = EnsureWhitespaceAfterAs(asTokenIndex); + + return nextToken; + } + + private int EnsureWhitespaceAfterAs(int asTokenIndex) + { + int nextToken = asTokenIndex + 1; + Debug.Assert(nextToken < CodeObject.Position.endTokenNumber, "View definition ends unexpectedly after the AS token."); + + TokenData nextTokenData = GetTokenData(nextToken); + // Ensure a whitespace after the "AS" token + if (!IsTokenWhitespace(nextTokenData)) + { + AddIndentedNewLineReplacement(nextTokenData.StartIndex); + } + + return nextToken; + } + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/WhiteSpaceSeparatedListFormatter.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/WhiteSpaceSeparatedListFormatter.cs new file mode 100644 index 00000000..4ac931d2 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/Impl/WhiteSpaceSeparatedListFormatter.cs @@ -0,0 +1,75 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + /// + /// Base class for a set of utility formatters that are used by Node-specific formatters when dealing with whitespace + /// + internal abstract class WhiteSpaceSeparatedListFormatter : ASTNodeFormatterT + { + private bool IncremenetIndentLevelOnPrefixRegion { get; set; } + + /// + /// This constructor initalizes the and properties since the formatter's entry point + /// is not the Format method + /// + /// + /// + /// + internal WhiteSpaceSeparatedListFormatter(FormatterVisitor visitor, SqlCodeObject codeObject, bool incrementIndentLevelOnPrefixRegion) + : base(visitor, codeObject) + { + IncremenetIndentLevelOnPrefixRegion = incrementIndentLevelOnPrefixRegion; + } + + internal override void ProcessPrefixRegion(int startTokenNumber, int firstChildStartTokenNumber) + { + if (IncremenetIndentLevelOnPrefixRegion) + { + IncrementIndentLevel(); + } + base.ProcessPrefixRegion(startTokenNumber, firstChildStartTokenNumber); + } + + internal override void ProcessSuffixRegion(int lastChildEndTokenNumber, int endTokenNumber) + { + if (IncremenetIndentLevelOnPrefixRegion) + { + DecrementIndentLevel(); + } + base.ProcessSuffixRegion(lastChildEndTokenNumber, endTokenNumber); + } + + internal override void ProcessInterChildRegion(SqlCodeObject previousChild, SqlCodeObject nextChild) + { + Validate.IsNotNull(nameof(previousChild), previousChild); + Validate.IsNotNull(nameof(nextChild), nextChild); + + int start = previousChild.Position.endTokenNumber; + int end = nextChild.Position.startTokenNumber; + + if (start < end) + { + for (int i = start; i < end; i++) + { + SimpleProcessToken(i, FormatWhitespace); + } + } + else + { + // Insert the minimum whitespace + string minWhite = FormatWhitespace(" ", Visitor.Context); + int insertLocation = TokenManager.TokenList[start].StartIndex; + AddReplacement(insertLocation, "", minWhite); + } + } + + internal abstract string FormatWhitespace(string original, FormatContext context); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Formatter/TSqlFormatterService.cs b/src/Microsoft.SqlTools.ServiceLayer/Formatter/TSqlFormatterService.cs new file mode 100644 index 00000000..c6afa157 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/TSqlFormatterService.cs @@ -0,0 +1,266 @@ +// +// 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.Collections.Generic; +using System.Composition; +using System.IO; +using System.Threading.Tasks; +using Microsoft.SqlServer.Management.SqlParser.Parser; +using Microsoft.SqlTools.ServiceLayer.Extensibility; +using Microsoft.SqlTools.ServiceLayer.Formatter.Contracts; +using Microsoft.SqlTools.ServiceLayer.Hosting; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; +using Microsoft.SqlTools.ServiceLayer.SqlContext; +using Microsoft.SqlTools.ServiceLayer.Utility; +using Microsoft.SqlTools.ServiceLayer.Workspace; +using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.Formatter +{ + + [Export(typeof(IHostedService))] + public class TSqlFormatterService : HostedService, IComposableService + { + private FormatterSettings settings; + /// + /// The default constructor is required for MEF-based composable services + /// + public TSqlFormatterService() + { + settings = new FormatterSettings(); + } + + public override void InitializeService(IProtocolEndpoint serviceHost) + { + serviceHost.SetRequestHandler(DocumentFormattingRequest.Type, HandleDocFormatRequest); + serviceHost.SetRequestHandler(DocumentRangeFormattingRequest.Type, HandleDocRangeFormatRequest); + + WorkspaceService?.RegisterConfigChangeCallback(HandleDidChangeConfigurationNotification); + } + + /// + /// Gets the workspace service. Note: should handle case where this is null in cases where unit tests do not set this up + /// + private WorkspaceService WorkspaceService + { + get { return ServiceProvider.GetService>(); } + } + + + /// + /// Ensure formatter settings are always up to date + /// + public Task HandleDidChangeConfigurationNotification( + SqlToolsSettings newSettings, + SqlToolsSettings oldSettings, + EventContext eventContext) + { + // update the current settings to reflect any changes (assuming formatter settings exist) + settings = newSettings.SqlTools.Format ?? settings; + return Task.FromResult(true); + } + + public async Task HandleDocFormatRequest(DocumentFormattingParams docFormatParams, RequestContext requestContext) + { + Func> requestHandler = () => + { + return FormatAndReturnEdits(docFormatParams); + }; + await HandleRequest(requestHandler, requestContext, "HandleDocFormatRequest"); + } + + public async Task HandleDocRangeFormatRequest(DocumentRangeFormattingParams docRangeFormatParams, RequestContext requestContext) + { + Func> requestHandler = () => + { + return FormatRangeAndReturnEdits(docRangeFormatParams); + }; + await HandleRequest(requestHandler, requestContext, "HandleDocRangeFormatRequest"); + } + + private async Task FormatRangeAndReturnEdits(DocumentRangeFormattingParams docFormatParams) + { + return await Task.Factory.StartNew(() => + { + var range = docFormatParams.Range; + ScriptFile scriptFile = GetFile(docFormatParams); + TextEdit textEdit = new TextEdit { Range = range }; + string text = scriptFile.GetTextInRange(range.ToBufferRange()); + return DoFormat(docFormatParams, textEdit, text); + }); + } + + private async Task FormatAndReturnEdits(DocumentFormattingParams docFormatParams) + { + return await Task.Factory.StartNew(() => + { + var scriptFile = GetFile(docFormatParams); + if (scriptFile.FileLines.Count == 0) + { + return new TextEdit[0]; + } + TextEdit textEdit = PrepareEdit(scriptFile); + string text = scriptFile.Contents; + return DoFormat(docFormatParams, textEdit, text); + }); + } + + private TextEdit[] DoFormat(DocumentFormattingParams docFormatParams, TextEdit edit, string text) + { + Validate.IsNotNull(nameof(docFormatParams), docFormatParams); + + FormatOptions options = GetOptions(docFormatParams); + List edits = new List(); + edit.NewText = Format(text, options, false); + // TODO do not add if no formatting needed? + edits.Add(edit); + return edits.ToArray(); + } + + private FormatOptions GetOptions(DocumentFormattingParams docFormatParams) + { + FormatOptions options = new FormatOptions(); + if (docFormatParams.Options != null) + { + options.UseSpaces = docFormatParams.Options.InsertSpaces; + options.SpacesPerIndent = docFormatParams.Options.TabSize; + } + if (settings != null) + { + if (settings.AlignColumnDefinitionsInColumns.HasValue) { options.AlignColumnDefinitionsInColumns = settings.AlignColumnDefinitionsInColumns.Value; } + + if (settings.PlaceCommasBeforeNextStatement.HasValue) { options.PlaceCommasBeforeNextStatement = settings.PlaceCommasBeforeNextStatement.Value; } + + if (settings.PlaceSelectStatementReferencesOnNewLine.HasValue) { options.PlaceEachReferenceOnNewLineInQueryStatements = settings.PlaceSelectStatementReferencesOnNewLine.Value; } + + if (settings.UseBracketForIdentifiers.HasValue) { options.EncloseIdentifiersInSquareBrackets = settings.UseBracketForIdentifiers.Value; } + + } + return options; + } + + internal static void UpdateFormatOptionsFromSettings(FormatOptions options, FormatterSettings settings) + { + Validate.IsNotNull(nameof(options), options); + if (settings != null) + { + if (settings.AlignColumnDefinitionsInColumns.HasValue) { options.AlignColumnDefinitionsInColumns = settings.AlignColumnDefinitionsInColumns.Value; } + + if (settings.PlaceCommasBeforeNextStatement.HasValue) { options.PlaceCommasBeforeNextStatement = settings.PlaceCommasBeforeNextStatement.Value; } + + if (settings.PlaceSelectStatementReferencesOnNewLine.HasValue) { options.PlaceEachReferenceOnNewLineInQueryStatements = settings.PlaceSelectStatementReferencesOnNewLine.Value; } + + if (settings.UseBracketForIdentifiers.HasValue) { options.EncloseIdentifiersInSquareBrackets = settings.UseBracketForIdentifiers.Value; } + + options.DatatypeCasing = settings.DatatypeCasing; + options.KeywordCasing = settings.KeywordCasing; + } + } + + private ScriptFile GetFile(DocumentFormattingParams docFormatParams) + { + return WorkspaceService.Workspace.GetFile(docFormatParams.TextDocument.Uri); + } + + private static TextEdit PrepareEdit(ScriptFile scriptFile) + { + int fileLines = scriptFile.FileLines.Count; + Position start = new Position { Line = 0, Character = 0 }; + int lastChar = scriptFile.FileLines[scriptFile.FileLines.Count - 1].Length; + Position end = new Position { Line = scriptFile.FileLines.Count - 1, Character = lastChar }; + + TextEdit edit = new TextEdit + { + Range = new Range { Start = start, End = end } + }; + return edit; + } + + private async Task HandleRequest(Func> handler, RequestContext requestContext, string requestType) + { + Logger.Write(LogLevel.Verbose, requestType); + + try + { + T result = await handler(); + await requestContext.SendResult(result); + } + catch (Exception ex) + { + await requestContext.SendError(ex.ToString()); + } + } + + + + public string Format(TextReader input) + { + string originalSql = input.ReadToEnd(); + return Format(originalSql, new FormatOptions()); + } + + public string Format(string input, FormatOptions options) + { + return Format(input, options, true); + } + + public string Format(string input, FormatOptions options, bool verifyOutput) + { + string result = null; + DoFormat(input, options, verifyOutput, visitor => + { + result = visitor.Context.FormattedSql; + }); + + return result; + } + + public void Format(string input, FormatOptions options, bool verifyOutput, Replacement.OnReplace replace) + { + DoFormat(input, options, verifyOutput, visitor => + { + foreach (Replacement r in visitor.Context.Replacements) + { + r.Apply(replace); + } + }); + } + + private void DoFormat(string input, FormatOptions options, bool verifyOutput, Action postFormatAction) + { + Validate.IsNotNull(nameof(input), input); + Validate.IsNotNull(nameof(options), options); + + ParseResult result = Parser.Parse(input); + FormatContext context = new FormatContext(result.Script, options); + + FormatterVisitor visitor = new FormatterVisitor(context, ServiceProvider); + result.Script.Accept(visitor); + if (verifyOutput) + { + visitor.VerifyFormat(); + } + + postFormatAction?.Invoke(visitor); + } + } + + internal static class RangeExtensions + { + public static BufferRange ToBufferRange(this Range range) + { + // It turns out that VSCode sends Range objects as 0-indexed lines, while + // our BufferPosition and BufferRange logic assumes 1-indexed. Therefore + // need to increment all ranges by 1 when copying internally and reduce + // when returning to the caller + return new BufferRange( + new BufferPosition(range.Start.Line + 1, range.Start.Character + 1), + new BufferPosition(range.End.Line + 1, range.End.Character + 1) + ); + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs b/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs new file mode 100644 index 00000000..429e3732 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs @@ -0,0 +1,105 @@ +// +// 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.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlTools.ServiceLayer.Credentials; +using Microsoft.SqlTools.ServiceLayer.Extensibility; +using Microsoft.SqlTools.ServiceLayer.Hosting; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.LanguageServices; +using Microsoft.SqlTools.ServiceLayer.QueryExecution; +using Microsoft.SqlTools.ServiceLayer.SqlContext; +using Microsoft.SqlTools.ServiceLayer.Workspace; + +namespace Microsoft.SqlTools.ServiceLayer +{ + /// + /// Provides support for starting up a service host. This is a common responsibility + /// for both the main service program and test driver that interacts with it + /// + public static class HostLoader + { + private static object lockObject = new object(); + private static bool isLoaded; + + internal static ServiceHost CreateAndStartServiceHost(SqlToolsContext sqlToolsContext) + { + ServiceHost serviceHost = ServiceHost.Instance; + lock (lockObject) + { + if (!isLoaded) + { + // Grab the instance of the service host + serviceHost.Initialize(); + + InitializeRequestHandlersAndServices(serviceHost, sqlToolsContext); + + // Start the service only after all request handlers are setup. This is vital + // as otherwise the Initialize event can be lost - it's processed and discarded before the handler + // is hooked up to receive the message + serviceHost.Start().Wait(); + isLoaded = true; + } + } + return serviceHost; + } + + private static void InitializeRequestHandlersAndServices(ServiceHost serviceHost, SqlToolsContext sqlToolsContext) + { + // Load extension provider, which currently finds all exports in current DLL. Can be changed to find based + // on directory or assembly list quite easily in the future + ExtensionServiceProvider serviceProvider = ExtensionServiceProvider.CreateDefaultServiceProvider(); + serviceProvider.RegisterSingleService(sqlToolsContext); + serviceProvider.RegisterSingleService(serviceHost); + + // Initialize and register singleton services so they're accessible for any MEF service. In the future, these + // could be updated to be IComposableServices, which would avoid the requirement to define a singleton instance + // and instead have MEF handle discovery & loading + WorkspaceService.Instance.InitializeService(serviceHost); + serviceProvider.RegisterSingleService(WorkspaceService.Instance); + + LanguageService.Instance.InitializeService(serviceHost, sqlToolsContext); + serviceProvider.RegisterSingleService(LanguageService.Instance); + + ConnectionService.Instance.InitializeService(serviceHost); + serviceProvider.RegisterSingleService(ConnectionService.Instance); + + CredentialService.Instance.InitializeService(serviceHost); + serviceProvider.RegisterSingleService(CredentialService.Instance); + + QueryExecutionService.Instance.InitializeService(serviceHost); + serviceProvider.RegisterSingleService(QueryExecutionService.Instance); + + InitializeHostedServices(serviceProvider, serviceHost); + + serviceHost.InitializeRequestHandlers(); + } + + /// + /// Internal to support testing. Initializes instances in the service, + /// and registers them for their preferred service type + /// + internal static void InitializeHostedServices(RegisteredServiceProvider provider, IProtocolEndpoint host) + { + // Pre-register all services before initializing. This ensures that if one service wishes to reference + // another one during initialization, it will be able to safely do so + foreach (IHostedService service in provider.GetServices()) + { + provider.RegisterSingleService(service.ServiceType, service); + } + + foreach (IHostedService service in provider.GetServices()) + { + // Initialize all hosted services, and register them in the service provider for their requested + // service type. This ensures that when searching for the ConnectionService you can get it without + // searching for an IHostedService of type ConnectionService + service.InitializeService(host); + } + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Contracts/ServerCapabilities.cs b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Contracts/ServerCapabilities.cs index 32f0e736..cba641da 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Contracts/ServerCapabilities.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Contracts/ServerCapabilities.cs @@ -21,6 +21,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting.Contracts public bool? DocumentHighlightProvider { get; set; } + public bool? DocumentFormattingProvider { get; set; } + + public bool? DocumentRangeFormattingProvider { get; set; } + public bool? DocumentSymbolProvider { get; set; } public bool? WorkspaceSymbolProvider { get; set; } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Hosting/IHostedService.cs b/src/Microsoft.SqlTools.ServiceLayer/Hosting/IHostedService.cs new file mode 100644 index 00000000..848c5820 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Hosting/IHostedService.cs @@ -0,0 +1,71 @@ +// +// 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 Microsoft.SqlTools.ServiceLayer.Extensibility; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; + +namespace Microsoft.SqlTools.ServiceLayer.Hosting +{ + /// + /// Defines a hosted service that communicates with external processes via + /// messages passed over the . The service defines + /// a standard initialization method where it can hook up to the host. + /// + public interface IHostedService + { + /// + /// Callback to initialize this service + /// + /// which supports registering + /// event handlers and other callbacks for messages passed to external callers + void InitializeService(IProtocolEndpoint serviceHost); + + /// + /// What is the service type that you wish to register? + /// + Type ServiceType { get; } + } + + /// + /// Base class for implementations that handles defining the + /// being registered. This simplifies service registration. This also implements which + /// allows injection of the service provider for lookup of other services. + /// + /// Extending classes should implement per below code example + /// + /// [Export(typeof(IHostedService)] + /// MyService : HostedService<MyService> + /// { + /// public override void InitializeService(IProtocolEndpoint serviceHost) + /// { + /// serviceHost.SetRequestHandler(MyRequest.Type, HandleMyRequest); + /// } + /// } + /// + /// + /// Type to be registered for lookup in the service provider + public abstract class HostedService : IHostedService, IComposableService + { + + protected IMultiServiceProvider ServiceProvider { get; private set; } + + public void SetServiceProvider(IMultiServiceProvider provider) + { + ServiceProvider = provider; + } + + public Type ServiceType + { + get + { + return typeof(T); + } + } + + public abstract void InitializeService(IProtocolEndpoint serviceHost); + + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/ProtocolEndpoint.cs b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/ProtocolEndpoint.cs index 2a0652ac..9c9eda9e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/ProtocolEndpoint.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Hosting/Protocol/ProtocolEndpoint.cs @@ -19,6 +19,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol /// public class ProtocolEndpoint : IProtocolEndpoint { + private bool isInitialized; private bool isStarted; private int currentMessageId; private ChannelBase protocolChannel; @@ -64,12 +65,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol } /// - /// Starts the language server client and sends the Initialize method. + /// Initializes /// - /// A Task that can be awaited for initialization to complete. - public async Task Start() + public void Initialize() { - if (!this.isStarted) + if (!this.isInitialized) { // Start the provided protocol channel this.protocolChannel.Start(this.messageProtocolType); @@ -83,6 +83,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol // Listen for unhandled exceptions from the dispatcher this.MessageDispatcher.UnhandledException += MessageDispatcher_UnhandledException; + this.isInitialized = true; + } + } + + /// + /// Starts the language server client and sends the Initialize method. + /// + /// A Task that can be awaited for initialization to complete. + public async Task Start() + { + if (!this.isStarted) + { + // Notify implementation about endpoint start await this.OnStart(); diff --git a/src/Microsoft.SqlTools.ServiceLayer/Hosting/ServiceHost.cs b/src/Microsoft.SqlTools.ServiceLayer/Hosting/ServiceHost.cs index 93136013..c33a289f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Hosting/ServiceHost.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Hosting/ServiceHost.cs @@ -58,7 +58,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting /// /// Provide initialization that must occur after the service host is started /// - public void Initialize() + public void InitializeRequestHandlers() { // Register the requests that this service host will handle this.SetRequestHandler(InitializeRequest.Type, this.HandleInitializeRequest); @@ -153,6 +153,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting TextDocumentSync = TextDocumentSyncKind.Incremental, DefinitionProvider = true, ReferencesProvider = false, + DocumentFormattingProvider = true, + DocumentRangeFormattingProvider = true, DocumentHighlightProvider = false, HoverProvider = true, CompletionProvider = new CompletionOptions diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/Completion.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/Completion.cs index 64c0464f..b018bc97 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/Completion.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/Completion.cs @@ -45,14 +45,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts Reference = 18 } - [DebuggerDisplay("NewText = {NewText}, Range = {Range.Start.Line}:{Range.Start.Character} - {Range.End.Line}:{Range.End.Character}")] - public class TextEdit - { - public Range Range { get; set; } - - public string NewText { get; set; } - } - [DebuggerDisplay("Kind = {Kind.ToString()}, Label = {Label}, Detail = {Detail}")] public class CompletionItem { diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/TextEdit.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/TextEdit.cs new file mode 100644 index 00000000..45769639 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/TextEdit.cs @@ -0,0 +1,20 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Diagnostics; +using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts +{ + + [DebuggerDisplay("NewText = {NewText}, Range = {Range.Start.Line}:{Range.Start.Character} - {Range.End.Line}:{Range.End.Character}")] + public class TextEdit + { + public Range Range { get; set; } + + public string NewText { get; set; } + } + +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Program.cs b/src/Microsoft.SqlTools.ServiceLayer/Program.cs index 25bc92b1..1d1310a7 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Program.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Program.cs @@ -4,11 +4,6 @@ using System; using Microsoft.SqlTools.ServiceLayer.Hosting; using Microsoft.SqlTools.ServiceLayer.SqlContext; -using Microsoft.SqlTools.ServiceLayer.Workspace; -using Microsoft.SqlTools.ServiceLayer.LanguageServices; -using Microsoft.SqlTools.ServiceLayer.Connection; -using Microsoft.SqlTools.ServiceLayer.QueryExecution; -using Microsoft.SqlTools.ServiceLayer.Credentials; using Microsoft.SqlTools.ServiceLayer.Utility; namespace Microsoft.SqlTools.ServiceLayer @@ -36,25 +31,13 @@ namespace Microsoft.SqlTools.ServiceLayer Logger.Write(LogLevel.Normal, "Starting SQL Tools Service Host"); // set up the host details and profile paths - var hostDetails = new HostDetails(version: new Version(1,0)); + var hostDetails = new HostDetails(version: new Version(1, 0)); SqlToolsContext sqlToolsContext = new SqlToolsContext(hostDetails); + ServiceHost serviceHost = HostLoader.CreateAndStartServiceHost(sqlToolsContext); - // Grab the instance of the service host - ServiceHost serviceHost = ServiceHost.Instance; - - // Start the service - serviceHost.Start().Wait(); - - // Initialize the services that will be hosted here - WorkspaceService.Instance.InitializeService(serviceHost); - LanguageService.Instance.InitializeService(serviceHost, sqlToolsContext); - ConnectionService.Instance.InitializeService(serviceHost); - CredentialService.Instance.InitializeService(serviceHost); - QueryExecutionService.Instance.InitializeService(serviceHost); - - serviceHost.Initialize(); serviceHost.WaitForExit(); } + } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlContext/FormatterSettings.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlContext/FormatterSettings.cs new file mode 100644 index 00000000..f6bb6bfe --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlContext/FormatterSettings.cs @@ -0,0 +1,88 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.ServiceLayer.Formatter; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Microsoft.SqlTools.ServiceLayer.SqlContext +{ + /// + /// Contract for receiving formatter-specific settings as part of workspace settings + /// + public class FormatterSettings + { + /// + /// Should names be escaped, for example converting dbo.T1 to [dbo].[T1] + /// + public bool? UseBracketForIdentifiers + { + get; + set; + } + + /// + /// Should comma separated lists have the comma be at the start of a new line. + /// + /// CREATE TABLE T1 ( + /// C1 INT + /// , C2 INT) + /// + /// + public bool? PlaceCommasBeforeNextStatement + { + get; + set; + } + + /// + /// Should each reference be on its own line or should references to multiple objects + /// be kept on a single line + /// + /// SELECT * + /// FROM T1, + /// T2 + /// + /// + public bool? PlaceSelectStatementReferencesOnNewLine + { + get; + set; + } + + /// + /// Should keyword casing be ignored, converted to all uppercase, or + /// converted to all lowercase + /// + [JsonConverter(typeof(StringEnumConverter))] + public CasingOptions KeywordCasing + { + get; + set; + } + + + /// + /// Should data type casing be ignored, converted to all uppercase, or + /// converted to all lowercase + /// + [JsonConverter(typeof(StringEnumConverter))] + public CasingOptions DatatypeCasing + { + get; + set; + } + + + /// + /// Should column definitions be aligned or left non-aligned? + /// + public bool? AlignColumnDefinitionsInColumns + { + get; + set; + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlContext/SqlToolsSettings.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlContext/SqlToolsSettings.cs index 0359df16..93f677ed 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlContext/SqlToolsSettings.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlContext/SqlToolsSettings.cs @@ -113,9 +113,9 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext /// public SqlToolsSettingsValues() { - - this.IntelliSense = new IntelliSenseSettings(); - this.QueryExecutionSettings = new QueryExecutionSettings(); + IntelliSense = new IntelliSenseSettings(); + QueryExecutionSettings = new QueryExecutionSettings(); + Format = new FormatterSettings(); } /// @@ -127,5 +127,11 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext /// Gets or sets the query execution settings /// public QueryExecutionSettings QueryExecutionSettings { get; set; } + + /// + /// Gets or sets the formatter settings + /// + [JsonProperty("format")] + public FormatterSettings Format { get; set; } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Workspace/Contracts/FilePosition.cs b/src/Microsoft.SqlTools.ServiceLayer/Workspace/Contracts/FilePosition.cs index 65fe268a..ae507af7 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Workspace/Contracts/FilePosition.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Workspace/Contracts/FilePosition.cs @@ -63,7 +63,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts /// A new FilePosition instance for the calculated position. public FilePosition AddOffset(int lineOffset, int columnOffset) { - return this.scriptFile.CalculatePosition( + return scriptFile.CalculatePosition( this, lineOffset, columnOffset); @@ -77,7 +77,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts /// A new FilePosition instance for the calculated position. public FilePosition GetLineStart() { - string scriptLine = scriptFile.FileLines[this.Line - 1]; + string scriptLine = scriptFile.FileLines[Line - 1]; int lineStartColumn = 1; for (int i = 0; i < scriptLine.Length; i++) @@ -89,7 +89,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts } } - return new FilePosition(this.scriptFile, this.Line, lineStartColumn); + return new FilePosition(scriptFile, Line, lineStartColumn); } /// @@ -99,8 +99,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts /// A new FilePosition instance for the calculated position. public FilePosition GetLineEnd() { - string scriptLine = scriptFile.FileLines[this.Line - 1]; - return new FilePosition(this.scriptFile, this.Line, scriptLine.Length + 1); + string scriptLine = scriptFile.FileLines[Line - 1]; + return new FilePosition(scriptFile, Line, scriptLine.Length + 1); } #endregion diff --git a/src/Microsoft.SqlTools.ServiceLayer/Workspace/Contracts/ScriptFile.cs b/src/Microsoft.SqlTools.ServiceLayer/Workspace/Contracts/ScriptFile.cs index 4cb5948c..463da178 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Workspace/Contracts/ScriptFile.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Workspace/Contracts/ScriptFile.cs @@ -61,11 +61,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts { get { - return string.Join("\r\n", this.FileLines); + return string.Join("\r\n", FileLines); } set { - this.FileLines = value != null ? value.Split('\n') : null; + FileLines = value != null ? value.Split('\n') : null; } } @@ -118,12 +118,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts string clientFilePath, TextReader textReader) { - this.FilePath = filePath; - this.ClientFilePath = clientFilePath; - this.IsAnalysisEnabled = true; - this.IsInMemory = Workspace.IsPathInMemory(filePath); + FilePath = filePath; + ClientFilePath = clientFilePath; + IsAnalysisEnabled = true; + IsInMemory = Workspace.IsPathInMemory(filePath); - this.SetFileContents(textReader.ReadToEnd()); + SetFileContents(textReader.ReadToEnd()); } /// @@ -137,11 +137,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts string clientFilePath, string initialBuffer) { - this.FilePath = filePath; - this.ClientFilePath = clientFilePath; - this.IsAnalysisEnabled = true; + FilePath = filePath; + ClientFilePath = clientFilePath; + IsAnalysisEnabled = true; - this.SetFileContents(initialBuffer); + SetFileContents(initialBuffer); } #endregion @@ -157,9 +157,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts { Validate.IsWithinRange( "lineNumber", lineNumber, - 1, this.FileLines.Count + 1); + 1, FileLines.Count + 1); - return this.FileLines[lineNumber - 1]; + return FileLines[lineNumber - 1]; + } + + /// + /// Gets the text under a specific range + /// + public string GetTextInRange(BufferRange range) + { + return string.Join(Environment.NewLine, GetLinesInRange(range)); } /// @@ -170,8 +178,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts /// An array of strings from the specified range of the file. public virtual string[] GetLinesInRange(BufferRange bufferRange) { - this.ValidatePosition(bufferRange.Start); - this.ValidatePosition(bufferRange.End); + ValidatePosition(bufferRange.Start); + ValidatePosition(bufferRange.End); List linesInRange = new List(); @@ -180,7 +188,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts for (int line = startLine; line <= endLine; line++) { - string currentLine = this.FileLines[line - 1]; + string currentLine = FileLines[line - 1]; int startColumn = line == startLine ? bufferRange.Start.Column @@ -208,7 +216,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts /// The position in the buffer to be validated. public void ValidatePosition(BufferPosition bufferPosition) { - this.ValidatePosition( + ValidatePosition( bufferPosition.Line, bufferPosition.Column); } @@ -221,14 +229,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts /// The 1-based column to be validated. public void ValidatePosition(int line, int column) { - if (line < 1 || line > this.FileLines.Count + 1) + if (line < 1 || line > FileLines.Count + 1) { throw new ArgumentOutOfRangeException(nameof(line), SR.WorkspaceServicePositionLineOutOfRange); } // The maximum column is either one past the length of the string // or 1 if the string is empty. - string lineString = this.FileLines[line - 1]; + string lineString = FileLines[line - 1]; int maxColumn = lineString.Length > 0 ? lineString.Length + 1 : 1; if (column < 1 || column > maxColumn) @@ -243,28 +251,28 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts /// The FileChange to apply to the file's contents. public void ApplyChange(FileChange fileChange) { - this.ValidatePosition(fileChange.Line, fileChange.Offset); - this.ValidatePosition(fileChange.EndLine, fileChange.EndOffset); + ValidatePosition(fileChange.Line, fileChange.Offset); + ValidatePosition(fileChange.EndLine, fileChange.EndOffset); // Break up the change lines string[] changeLines = fileChange.InsertString.Split('\n'); // Get the first fragment of the first line string firstLineFragment = - this.FileLines[fileChange.Line - 1] + FileLines[fileChange.Line - 1] .Substring(0, fileChange.Offset - 1); // Get the last fragment of the last line - string endLine = this.FileLines[fileChange.EndLine - 1]; + string endLine = FileLines[fileChange.EndLine - 1]; string lastLineFragment = endLine.Substring( fileChange.EndOffset - 1, - (this.FileLines[fileChange.EndLine - 1].Length - fileChange.EndOffset) + 1); + (FileLines[fileChange.EndLine - 1].Length - fileChange.EndOffset) + 1); // Remove the old lines for (int i = 0; i <= fileChange.EndLine - fileChange.Line; i++) { - this.FileLines.RemoveAt(fileChange.Line - 1); + FileLines.RemoveAt(fileChange.Line - 1); } // Build and insert the new lines @@ -287,7 +295,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts finalLine = finalLine + lastLineFragment; } - this.FileLines.Insert(currentLineNumber - 1, finalLine); + FileLines.Insert(currentLineNumber - 1, finalLine); currentLineNumber++; } } @@ -301,7 +309,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts /// The zero-based offset for the given file position. public int GetOffsetAtPosition(int lineNumber, int columnNumber) { - Validate.IsWithinRange("lineNumber", lineNumber, 1, this.FileLines.Count); + Validate.IsWithinRange("lineNumber", lineNumber, 1, FileLines.Count); Validate.IsGreaterThan("columnNumber", columnNumber, 0); int offset = 0; @@ -316,7 +324,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts else { // Add an offset to account for the current platform's newline characters - offset += this.FileLines[i].Length + Environment.NewLine.Length; + offset += FileLines[i].Length + Environment.NewLine.Length; } } @@ -339,9 +347,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts int newLine = originalPosition.Line + lineOffset, newColumn = originalPosition.Column + columnOffset; - this.ValidatePosition(newLine, newColumn); + ValidatePosition(newLine, newColumn); - string scriptLine = this.FileLines[newLine - 1]; + string scriptLine = FileLines[newLine - 1]; newColumn = Math.Min(scriptLine.Length + 1, newColumn); return new FilePosition(this, newLine, newColumn); @@ -379,9 +387,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts BufferPosition endPosition = startPosition; int line = 0; - while (line < this.FileLines.Count) + while (line < FileLines.Count) { - if (searchedOffset <= currentOffset + this.FileLines[line].Length) + if (searchedOffset <= currentOffset + FileLines[line].Length) { int column = searchedOffset - currentOffset; @@ -415,7 +423,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts } // Increase the current offset and include newline length - currentOffset += this.FileLines[line].Length + Environment.NewLine.Length; + currentOffset += FileLines[line].Length + Environment.NewLine.Length; line++; } @@ -430,7 +438,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts { // Split the file contents into lines and trim // any carriage returns from the strings. - this.FileLines = + FileLines = fileContents .Split('\n') .Select(line => line.TrimEnd('\r')) diff --git a/src/Microsoft.SqlTools.ServiceLayer/project.json b/src/Microsoft.SqlTools.ServiceLayer/project.json index ab58448a..3e0b83db 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/project.json +++ b/src/Microsoft.SqlTools.ServiceLayer/project.json @@ -28,8 +28,15 @@ "NETStandard.Library": "1.6.0", "Microsoft.NETCore.Runtime.CoreCLR": "1.0.2", "Microsoft.NETCore.DotNetHostPolicy": "1.0.1", + "Microsoft.DiaSymReader.Native": "1.4.1", "System.Diagnostics.Process": "4.1.0", "System.Threading.Thread": "4.0.0", + "System.Runtime.Loader": "4.0.0", + "System.Composition.Runtime": "1.0.31", + "System.Composition.Convention": "1.0.31", + "System.Composition.TypedParts": "1.0.31", + "Microsoft.Extensions.DependencyModel": "1.0.0", + "System.Runtime": "4.3.0", "Microsoft.DiaSymReader.Native": "1.4.1" }, "frameworks": { diff --git a/src/Microsoft.SqlTools.ServiceLayer/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/sr.cs index 3bb109f3..dd310334 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/sr.cs @@ -117,6 +117,62 @@ namespace Microsoft.SqlTools.ServiceLayer } } + public static string ServiceAlreadyRegistered + { + get + { + return Keys.GetString(Keys.ServiceAlreadyRegistered); + } + } + + public static string MultipleServicesFound + { + get + { + return Keys.GetString(Keys.MultipleServicesFound); + } + } + + public static string IncompatibleServiceForExtensionLoader + { + get + { + return Keys.GetString(Keys.IncompatibleServiceForExtensionLoader); + } + } + + public static string ServiceProviderNotSet + { + get + { + return Keys.GetString(Keys.ServiceProviderNotSet); + } + } + + public static string ServiceNotFound + { + get + { + return Keys.GetString(Keys.ServiceNotFound); + } + } + + public static string ServiceNotOfExpectedType + { + get + { + return Keys.GetString(Keys.ServiceNotOfExpectedType); + } + } + + public static string ErrorUnexpectedCodeObjectType + { + get + { + return Keys.GetString(Keys.ErrorUnexpectedCodeObjectType); + } + } + public static string HostingUnexpectedEndOfStream { get @@ -413,6 +469,14 @@ namespace Microsoft.SqlTools.ServiceLayer } } + public static string ErrorEmptyStringReplacement + { + get + { + return Keys.GetString(Keys.ErrorEmptyStringReplacement); + } + } + public static string WorkspaceServicePositionLineOutOfRange { get @@ -774,6 +838,27 @@ namespace Microsoft.SqlTools.ServiceLayer public const string CredentialServiceWin32CredentialDisposed = "CredentialServiceWin32CredentialDisposed"; + public const string ServiceAlreadyRegistered = "ServiceAlreadyRegistered"; + + + public const string MultipleServicesFound = "MultipleServicesFound"; + + + public const string IncompatibleServiceForExtensionLoader = "IncompatibleServiceForExtensionLoader"; + + + public const string ServiceProviderNotSet = "ServiceProviderNotSet"; + + + public const string ServiceNotFound = "ServiceNotFound"; + + + public const string ServiceNotOfExpectedType = "ServiceNotOfExpectedType"; + + + public const string ErrorUnexpectedCodeObjectType = "ErrorUnexpectedCodeObjectType"; + + public const string HostingUnexpectedEndOfStream = "HostingUnexpectedEndOfStream"; @@ -903,6 +988,9 @@ namespace Microsoft.SqlTools.ServiceLayer public const string PeekDefinitionTypeNotSupportedError = "PeekDefinitionTypeNotSupportedError"; + public const string ErrorEmptyStringReplacement = "ErrorEmptyStringReplacement"; + + public const string WorkspaceServicePositionLineOutOfRange = "WorkspaceServicePositionLineOutOfRange"; diff --git a/src/Microsoft.SqlTools.ServiceLayer/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/sr.resx index b3fdd780..d9af1add 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/sr.resx @@ -181,6 +181,34 @@ Win32Credential object is already disposed + + Cannot register service for type {0}, one or more services already registered + + + + Multiple services found for type {0}, expected only 1 + + + + Service of type {0} cannot be created by ExtensionLoader<{1}> + + + + SetServiceProvider() was not called to establish the required service provider + + + + Service {0} was not found in the service provider + + + + Service of Type {0} is not compatible with registered Type {1} + + + + Cannot convert SqlCodeObject Type {0} to Type {1} + + MessageReader's input stream ended unexpectedly, terminating @@ -359,6 +387,10 @@ This object type is currently not supported by this feature. + + Replacement of an empty string by an empty string. + + Position is outside of file line range diff --git a/src/Microsoft.SqlTools.ServiceLayer/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/sr.strings index 6757d0df..90ea969c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/sr.strings @@ -59,6 +59,21 @@ CredentialsServiceTargetForLookup = Target must be specified to check existance CredentialServiceWin32CredentialDisposed = Win32Credential object is already disposed +############################################################################ +# Extensibility + +ServiceAlreadyRegistered = Cannot register service for type {0}, one or more services already registered +MultipleServicesFound = Multiple services found for type {0}, expected only 1 +IncompatibleServiceForExtensionLoader = Service of type {0} cannot be created by ExtensionLoader<{1}> +ServiceProviderNotSet = SetServiceProvider() was not called to establish the required service provider +ServiceNotFound = Service {0} was not found in the service provider +ServiceNotOfExpectedType = Service of Type {0} is not compatible with registered Type {1} + +############################################################################ +# Formatter + +ErrorUnexpectedCodeObjectType = Cannot convert SqlCodeObject Type {0} to Type {1} + ############################################################################ # Hosting @@ -168,6 +183,8 @@ PeekDefinitionTimedoutError = Operation timed out. PeekDefinitionTypeNotSupportedError = This object type is currently not supported by this feature. +ErrorEmptyStringReplacement = Replacement of an empty string by an empty string. + ############################################################################ # Workspace Service diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/ComparisonFailureException.cs b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/ComparisonFailureException.cs new file mode 100644 index 00000000..f9981550 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/ComparisonFailureException.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; + +namespace Microsoft.SqlTools.ServiceLayer.Test.Common +{ + internal class ComparisonFailureException : InvalidOperationException + { + internal string FullMessageWithDiff { get; private set; } + internal string EditAndCopyMessage { get; private set; } + + internal ComparisonFailureException(string fullMessageWithDiff, string editAndCopyMessage) + : base(fullMessageWithDiff) + { + FullMessageWithDiff = fullMessageWithDiff; + EditAndCopyMessage = editAndCopyMessage; + } + + internal ComparisonFailureException(string editAndCopyMessage) + : base(editAndCopyMessage) + { + EditAndCopyMessage = FullMessageWithDiff = editAndCopyMessage; + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/RunEnvironmentInfo.cs b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/RunEnvironmentInfo.cs index 7f0b753b..2889214e 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/RunEnvironmentInfo.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/RunEnvironmentInfo.cs @@ -1,3 +1,4 @@ + // // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. @@ -5,10 +6,15 @@ using System; using System.IO; +using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; namespace Microsoft.SqlTools.ServiceLayer.Test.Common { + /// + /// Contains environment information needed when running tests. + /// public class RunEnvironmentInfo { private static string cachedTestFolderPath; @@ -30,7 +36,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Common public static string GetTestDataLocation() { string testFolderPath; - string testPath = @"test\Microsoft.SqlTools.ServiceLayer.Test.Common\TestData"; + string testPath = Path.Combine("test", "Microsoft.SqlTools.ServiceLayer.Test.Common", "TestData"); string projectPath = Environment.GetEnvironmentVariable(Constants.ProjectPath); if (projectPath != null) @@ -45,13 +51,23 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Common } else { - string defaultPath = Path.Combine(typeof(Scripts).GetTypeInfo().Assembly.Location, @"..\..\..\..\.."); - testFolderPath = Path.Combine(defaultPath, @"Microsoft.SqlTools.ServiceLayer.Test.Common\TestData"); + // We are running tests locally, which means we expect to be running inside the bin\debug\netcoreapp directory + // Test Files should be found at the root of the project so go back the necessary number of directories for this + // to be found. We are manually specifying the testFolderPath here for clarity on where to expect this + + string assemblyDir = Path.GetDirectoryName(typeof(Scripts).GetTypeInfo().Assembly.Location); + string defaultPath = Path.Combine(assemblyDir, GoUpNDirectories(4)); + testFolderPath = Path.Combine(defaultPath, "Microsoft.SqlTools.ServiceLayer.Test.Common", "TestData"); + cachedTestFolderPath = testFolderPath; } } return testFolderPath; } - + private static string GoUpNDirectories(int n) + { + string up = ".." + (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "\\" : "/"); + return string.Concat(Enumerable.Repeat(up, n)); + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestConfigPersistenceHelper.cs b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestConfigPersistenceHelper.cs index b790dfb9..074e13a5 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestConfigPersistenceHelper.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestConfigPersistenceHelper.cs @@ -78,7 +78,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Common return Enumerable.Empty(); } } - catch (Exception ex) + catch (Exception) { return Enumerable.Empty(); } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/BQE_IndentOperands.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/BQE_IndentOperands.sql new file mode 100644 index 00000000..c4481069 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/BQE_IndentOperands.sql @@ -0,0 +1,5 @@ + select * + from mytable +intersect + select * + from mytable \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/BQE_KeywordCasing_LowerCase.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/BQE_KeywordCasing_LowerCase.sql new file mode 100644 index 00000000..de979b13 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/BQE_KeywordCasing_LowerCase.sql @@ -0,0 +1,15 @@ + + select * + from mytable +union + select * + from mytable +union all + select * + from mytable +except + select * + from mytable +intersect + select * + from mytable \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/BQE_KeywordCasing_NoFormat.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/BQE_KeywordCasing_NoFormat.sql new file mode 100644 index 00000000..1967b68f --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/BQE_KeywordCasing_NoFormat.sql @@ -0,0 +1,15 @@ + + seLect * + from mytable +unIon + selECT * + fROM mytable +union ALL + select * + from mytable +excepT + select * + from mytable +Intersect + select * + from mytable \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/BQE_KeywordCasing_UpperCase.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/BQE_KeywordCasing_UpperCase.sql new file mode 100644 index 00000000..92e83f0e --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/BQE_KeywordCasing_UpperCase.sql @@ -0,0 +1,15 @@ + + SELECT * + FROM mytable +UNION + SELECT * + FROM mytable +UNION ALL + SELECT * + FROM mytable +EXCEPT + SELECT * + FROM mytable +INTERSECT + SELECT * + FROM mytable \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/BQE_OperatorsOnNewLine.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/BQE_OperatorsOnNewLine.sql new file mode 100644 index 00000000..dbba4f22 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/BQE_OperatorsOnNewLine.sql @@ -0,0 +1,14 @@ + select * + from mytable +union + select * + from mytable +except + select * + from mytable +union all + select * + from mytable +intersect + select * + from mytable \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE.sql new file mode 100644 index 00000000..cbfb31a3 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE.sql @@ -0,0 +1,19 @@ +USE AdventureWorks2008R2; +GO +-- Define the CTE expression name and column list. +WITH + Sales_CTE (SalesPersonID, SalesOrderID, SalesYear) + AS + -- Define the CTE query. + ( + SELECT SalesPersonID, SalesOrderID, YEAR(OrderDate) AS SalesYear + FROM Sales.SalesOrderHeader + WHERE SalesPersonID IS NOT NULL + ) +-- Define the outer query referencing the CTE name. +SELECT SalesPersonID, COUNT(SalesOrderID) AS TotalSales, SalesYear +FROM Sales_CTE +GROUP BY SalesYear, SalesPersonID +ORDER BY SalesPersonID, SalesYear; +GO + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_20Spaces.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_20Spaces.sql new file mode 100644 index 00000000..f337650b --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_20Spaces.sql @@ -0,0 +1,32 @@ +USE AdventureWorks2008R2; +GO +-- Define the CTE expression name and column list. +WITH + Sales_CTE + ( + SalesPersonID, + SalesOrderID, + SalesYear + ) + AS + -- Define the CTE query. + ( + SELECT + SalesPersonID, + SalesOrderID, + YEAR(OrderDate) AS SalesYear + FROM + Sales.SalesOrderHeader + WHERE SalesPersonID IS NOT NULL + ) +-- Define the outer query referencing the CTE name. +SELECT + SalesPersonID, + COUNT(SalesOrderID) AS TotalSales, + SalesYear +FROM + Sales_CTE +GROUP BY SalesYear, SalesPersonID +ORDER BY SalesPersonID, SalesYear; +GO + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_CommasBeforeDefinition.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_CommasBeforeDefinition.sql new file mode 100644 index 00000000..f2ada26d --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_CommasBeforeDefinition.sql @@ -0,0 +1,19 @@ +USE AdventureWorks2008R2; +GO +-- Define the CTE expression name and column list. +WITH + Sales_CTE (SalesPersonID ,SalesOrderID ,SalesYear) + AS + -- Define the CTE query. + ( + SELECT SalesPersonID ,SalesOrderID ,YEAR(OrderDate) AS SalesYear + FROM Sales.SalesOrderHeader + WHERE SalesPersonID IS NOT NULL + ) +-- Define the outer query referencing the CTE name. +SELECT SalesPersonID ,COUNT(SalesOrderID) AS TotalSales ,SalesYear +FROM Sales_CTE +GROUP BY SalesYear, SalesPersonID +ORDER BY SalesPersonID, SalesYear; +GO + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_EachReferenceOnNewLine.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_EachReferenceOnNewLine.sql new file mode 100644 index 00000000..d2494a15 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_EachReferenceOnNewLine.sql @@ -0,0 +1,32 @@ +USE AdventureWorks2008R2; +GO +-- Define the CTE expression name and column list. +WITH + Sales_CTE + ( + SalesPersonID, + SalesOrderID, + SalesYear + ) + AS + -- Define the CTE query. + ( + SELECT + SalesPersonID, + SalesOrderID, + YEAR(OrderDate) AS SalesYear + FROM + Sales.SalesOrderHeader + WHERE SalesPersonID IS NOT NULL + ) +-- Define the outer query referencing the CTE name. +SELECT + SalesPersonID, + COUNT(SalesOrderID) AS TotalSales, + SalesYear +FROM + Sales_CTE +GROUP BY SalesYear, SalesPersonID +ORDER BY SalesPersonID, SalesYear; +GO + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_EachReferenceOnNewLine_CommasBeforeDefinition.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_EachReferenceOnNewLine_CommasBeforeDefinition.sql new file mode 100644 index 00000000..bdc47e57 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_EachReferenceOnNewLine_CommasBeforeDefinition.sql @@ -0,0 +1,32 @@ +USE AdventureWorks2008R2; +GO +-- Define the CTE expression name and column list. +WITH + Sales_CTE + ( + SalesPersonID + ,SalesOrderID + ,SalesYear + ) + AS + -- Define the CTE query. + ( + SELECT + SalesPersonID + ,SalesOrderID + ,YEAR(OrderDate) AS SalesYear + FROM + Sales.SalesOrderHeader + WHERE SalesPersonID IS NOT NULL + ) +-- Define the outer query referencing the CTE name. +SELECT + SalesPersonID + ,COUNT(SalesOrderID) AS TotalSales + ,SalesYear +FROM + Sales_CTE +GROUP BY SalesYear, SalesPersonID +ORDER BY SalesPersonID, SalesYear; +GO + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_LowerCaseKeywords.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_LowerCaseKeywords.sql new file mode 100644 index 00000000..149c982d --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_LowerCaseKeywords.sql @@ -0,0 +1,32 @@ +use AdventureWorks2008R2; +go +-- Define the CTE expression name and column list. +with + Sales_CTE + ( + SalesPersonID, + SalesOrderID, + SalesYear + ) + as + -- Define the CTE query. + ( + select + SalesPersonID, + SalesOrderID, + YEAR(OrderDate) as SalesYear + from + Sales.SalesOrderHeader + where SalesPersonID is not null + ) +-- Define the outer query referencing the CTE name. +select + SalesPersonID, + COUNT(SalesOrderID) as TotalSales, + SalesYear +from + Sales_CTE +group by SalesYear, SalesPersonID +order by SalesPersonID, SalesYear; +go + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_MultipleExpressions.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_MultipleExpressions.sql new file mode 100644 index 00000000..be00e55b --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_MultipleExpressions.sql @@ -0,0 +1,17 @@ +with + my_initial_table( column1, column2 ) + AS + ( + select * + from mytable + ), + my_other_table( column1X, column2X ) + AS + ( + select * + from mytable2 + ) +select distinct top (10) PERCENT with ties + alias1 = SIZE(mytable.mycol1), another = COUNT(new_elements), count(money) AS encore, id +INTO my_new_table +FROM my_initial_table \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_OneColumn.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_OneColumn.sql new file mode 100644 index 00000000..2d753d2f --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_OneColumn.sql @@ -0,0 +1,17 @@ +USE AdventureWorks2008R2; +GO +-- Define the CTE expression name and column list. +WITH + Sales_CTE (SalesOrderID) + AS + -- Define the CTE query. + ( + SELECT SalesOrderID + FROM Sales.SalesOrderHeader + WHERE SalesPersonID IS NOT NULL + ) +-- Define the outer query referencing the CTE name. +SELECT COUNT(SalesOrderID) AS TotalSales +FROM Sales_CTE; +GO + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_UpperCaseKeywords.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_UpperCaseKeywords.sql new file mode 100644 index 00000000..d2494a15 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_UpperCaseKeywords.sql @@ -0,0 +1,32 @@ +USE AdventureWorks2008R2; +GO +-- Define the CTE expression name and column list. +WITH + Sales_CTE + ( + SalesPersonID, + SalesOrderID, + SalesYear + ) + AS + -- Define the CTE query. + ( + SELECT + SalesPersonID, + SalesOrderID, + YEAR(OrderDate) AS SalesYear + FROM + Sales.SalesOrderHeader + WHERE SalesPersonID IS NOT NULL + ) +-- Define the outer query referencing the CTE name. +SELECT + SalesPersonID, + COUNT(SalesOrderID) AS TotalSales, + SalesYear +FROM + Sales_CTE +GROUP BY SalesYear, SalesPersonID +ORDER BY SalesPersonID, SalesYear; +GO + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_UseTabs.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_UseTabs.sql new file mode 100644 index 00000000..6716f8de --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CTE_UseTabs.sql @@ -0,0 +1,32 @@ +USE AdventureWorks2008R2; +GO +-- Define the CTE expression name and column list. +WITH + Sales_CTE + ( + SalesPersonID, + SalesOrderID, + SalesYear + ) + AS + -- Define the CTE query. + ( + SELECT + SalesPersonID, + SalesOrderID, + YEAR(OrderDate) AS SalesYear + FROM + Sales.SalesOrderHeader + WHERE SalesPersonID IS NOT NULL + ) +-- Define the outer query referencing the CTE name. +SELECT + SalesPersonID, + COUNT(SalesOrderID) AS TotalSales, + SalesYear +FROM + Sales_CTE +GROUP BY SalesYear, SalesPersonID +ORDER BY SalesPersonID, SalesYear; +GO + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_BackwardsCompatible.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_BackwardsCompatible.sql new file mode 100644 index 00000000..12e6a6a5 Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_BackwardsCompatible.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_BeginEnd.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_BeginEnd.sql new file mode 100644 index 00000000..291212c7 Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_BeginEnd.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_Minimal.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_Minimal.sql new file mode 100644 index 00000000..bd8b987f --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_Minimal.sql @@ -0,0 +1,4 @@ + +-- my comment +Create Procedure P1 +AS \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_MultipleBatches.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_MultipleBatches.sql new file mode 100644 index 00000000..06450016 Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_MultipleBatches.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_MultipleParams.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_MultipleParams.sql new file mode 100644 index 00000000..2da484e1 Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_MultipleParams.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_OneParam.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_OneParam.sql new file mode 100644 index 00000000..fa2847d8 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_OneParam.sql @@ -0,0 +1,3 @@ +Create Procedure dbo.P1 + @param1 int +AS \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_ParamsRecompileReturn.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_ParamsRecompileReturn.sql new file mode 100644 index 00000000..20e86813 Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_ParamsRecompileReturn.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_Select.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_Select.sql new file mode 100644 index 00000000..82de07c4 Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_Select.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_TwoPartName.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_TwoPartName.sql new file mode 100644 index 00000000..a1dd20ef --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_TwoPartName.sql @@ -0,0 +1,2 @@ +Create Procedure dbo.P1 +AS \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_WithCTE.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_WithCTE.sql new file mode 100644 index 00000000..1f612471 Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_WithCTE.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_WithEncryptionModule.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_WithEncryptionModule.sql new file mode 100644 index 00000000..4746cf3f Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_WithEncryptionModule.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_WithExecuteAsModule.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_WithExecuteAsModule.sql new file mode 100644 index 00000000..2ecf1515 Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_WithExecuteAsModule.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_WithThreeModules.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_WithThreeModules.sql new file mode 100644 index 00000000..40eb8b04 Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateProcedure_WithThreeModules.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable.sql new file mode 100644 index 00000000..64740fbc --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable.sql @@ -0,0 +1,18 @@ + +-- this is a comment before create table +CREATE TABLE [SimpleTable] +( + + -- this is a comment before document + [DocumentID] INT IDENTITY (1, 1) NOT NULL, + + -- this is a comment before Title + [Title] NVARCHAR (50) NOT NULL, + + + -- this is a comment before FileName + [FileName] NVARCHAR (400) NOT NULL, + + -- this is a comment before FileExtension + [FileExtension] nvarchar(8) +); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTableAddress_AlignInColumns.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTableAddress_AlignInColumns.sql new file mode 100644 index 00000000..9d733b69 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTableAddress_AlignInColumns.sql @@ -0,0 +1,22 @@ +CREATE TABLE [Person].[Address] +( + [AddressID] INT IDENTITY (1, 1) NOT FOR REPLICATION NOT NULL, + CONSTRAINT [PK_Address_AddressID] PRIMARY KEY CLUSTERED ([AddressID] ASC), + + [AddressLine1] NVARCHAR (60) NOT NULL, + + [AddressLine2] NVARCHAR (60) NULL, + + Address NVarChar (60) Null, + + [City] NVARCHAR (30) NOT NULL, + + [StateProvinceID] INT NOT NULL, + + [PostalCode] NVARCHAR (15) NOT NULL, + [rowguid] UNIQUEIDENTIFIER CONSTRAINT [DF_Address_rowguid] DEFAULT (newid()) ROWGUIDCOL NOT NULL, + [ModifiedDate] DATETIME CONSTRAINT [DF_Address_ModifiedDate] DEFAULT (getdate()) NOT NULL, + + + CONSTRAINT [FK_Address_StateProvince_StateProvinceID] FOREIGN KEY ([StateProvinceID]) REFERENCES [Person].[StateProvince] ([StateProvinceID]) ON DELETE NO ACTION ON UPDATE NO ACTION +); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTableAddress_AlignInColumnsUseTabs.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTableAddress_AlignInColumnsUseTabs.sql new file mode 100644 index 00000000..1ef3f37d --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTableAddress_AlignInColumnsUseTabs.sql @@ -0,0 +1,22 @@ +CREATE TABLE [Person].[Address] +( + [AddressID] INT IDENTITY (1, 1) NOT FOR REPLICATION NOT NULL, + CONSTRAINT [PK_Address_AddressID] PRIMARY KEY CLUSTERED ([AddressID] ASC), + + [AddressLine1] NVARCHAR (60) NOT NULL, + + [AddressLine2] NVARCHAR (60) NULL, + + Address NVarChar (60) Null, + + [City] NVARCHAR (30) NOT NULL, + + [StateProvinceID] INT NOT NULL, + + [PostalCode] NVARCHAR (15) NOT NULL, + [rowguid] UNIQUEIDENTIFIER CONSTRAINT [DF_Address_rowguid] DEFAULT (newid()) ROWGUIDCOL NOT NULL, + [ModifiedDate] DATETIME CONSTRAINT [DF_Address_ModifiedDate] DEFAULT (getdate()) NOT NULL, + + + CONSTRAINT [FK_Address_StateProvince_StateProvinceID] FOREIGN KEY ([StateProvinceID]) REFERENCES [Person].[StateProvince] ([StateProvinceID]) ON DELETE NO ACTION ON UPDATE NO ACTION +); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTableOn.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTableOn.sql new file mode 100644 index 00000000..a132bd7e --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTableOn.sql @@ -0,0 +1,9 @@ +CREATE TABLE db_name.schema_name.table_name +( + col_name1 INT, + col_name2 VARCHAR (20) + + + -- here is my comment +) + ON partition_schema_name (partition_column_name); \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_20Spaces.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_20Spaces.sql new file mode 100644 index 00000000..3280200a --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_20Spaces.sql @@ -0,0 +1,18 @@ + +-- this is a comment before create table +CREATE TABLE [SimpleTable] +( + + -- this is a comment before document + [DocumentID] INT IDENTITY (1, 1) NOT NULL, + + -- this is a comment before Title + [Title] NVARCHAR (50) NOT NULL, + + + -- this is a comment before FileName + [FileName] NVARCHAR (400) NOT NULL, + + -- this is a comment before FileExtension + [FileExtension] nvarchar(8) +); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_AlignInColumns.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_AlignInColumns.sql new file mode 100644 index 00000000..6ff0549f --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_AlignInColumns.sql @@ -0,0 +1,18 @@ + +-- this is a comment before create table +CREATE TABLE [SimpleTable] +( + + -- this is a comment before document + [DocumentID] INT IDENTITY (1, 1) NOT NULL, + + -- this is a comment before Title + [Title] NVARCHAR (50) NOT NULL, + + + -- this is a comment before FileName + [FileName] NVARCHAR (400) NOT NULL, + + -- this is a comment before FileExtension + [FileExtension] nvarchar(8) +); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_AlignInColumnsUseTabs.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_AlignInColumnsUseTabs.sql new file mode 100644 index 00000000..b60f7c8f --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_AlignInColumnsUseTabs.sql @@ -0,0 +1,18 @@ + +-- this is a comment before create table +CREATE TABLE [SimpleTable] +( + + -- this is a comment before document + [DocumentID] INT IDENTITY (1, 1) NOT NULL, + + -- this is a comment before Title + [Title] NVARCHAR (50) NOT NULL, + + + -- this is a comment before FileName + [FileName] NVARCHAR (400) NOT NULL, + + -- this is a comment before FileExtension + [FileExtension] nvarchar(8) +); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_CommasBeforeDefinition.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_CommasBeforeDefinition.sql new file mode 100644 index 00000000..bd88941c --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_CommasBeforeDefinition.sql @@ -0,0 +1,18 @@ + +-- this is a comment before create table +CREATE TABLE [SimpleTable] +( + + -- this is a comment before document + [DocumentID] INT IDENTITY (1, 1) NOT NULL + + -- this is a comment before Title + ,[Title] NVARCHAR (50) NOT NULL + + + -- this is a comment before FileName + ,[FileName] NVARCHAR (400) NOT NULL + + -- this is a comment before FileExtension + ,[FileExtension] nvarchar(8) +); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_CommentBeforeComma.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_CommentBeforeComma.sql new file mode 100644 index 00000000..57d26fc9 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_CommentBeforeComma.sql @@ -0,0 +1,13 @@ + +create table t1 +( + id INT not null /* awesome */ + ,x INT not null + /* cool */ + ,y INT not null + -- tremendeous +) +with +( + data_compression = row +); \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_Formatted.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_Formatted.sql new file mode 100644 index 00000000..e16e281d --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_Formatted.sql @@ -0,0 +1,7 @@ + +create table t1 +( + col1 int, + + col2 int +) \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_LowerCaseDataTypes.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_LowerCaseDataTypes.sql new file mode 100644 index 00000000..3a357be2 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_LowerCaseDataTypes.sql @@ -0,0 +1,18 @@ + +-- this is a comment before create table +CREATE TABLE [SimpleTable] +( + + -- this is a comment before document + [DocumentID] int IDENTITY (1, 1) NOT NULL, + + -- this is a comment before Title + [Title] nvarchar (50) NOT NULL, + + + -- this is a comment before FileName + [FileName] nvarchar (400) NOT NULL, + + -- this is a comment before FileExtension + [FileExtension] nvarchar(8) +); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_LowerCaseKeywords.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_LowerCaseKeywords.sql new file mode 100644 index 00000000..437816aa --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_LowerCaseKeywords.sql @@ -0,0 +1,18 @@ + +-- this is a comment before create table +create table [SimpleTable] +( + + -- this is a comment before document + [DocumentID] INT identity (1, 1) not null, + + -- this is a comment before Title + [Title] NVARCHAR (50) not null, + + + -- this is a comment before FileName + [FileName] NVARCHAR (400) not null, + + -- this is a comment before FileExtension + [FileExtension] nvarchar(8) +); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_Timestamp.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_Timestamp.sql new file mode 100644 index 00000000..18a7ff18 Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_Timestamp.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_UpperCaseDataTypes.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_UpperCaseDataTypes.sql new file mode 100644 index 00000000..8f9cd99b --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_UpperCaseDataTypes.sql @@ -0,0 +1,18 @@ + +-- this is a comment before create table +CREATE TABLE [SimpleTable] +( + + -- this is a comment before document + [DocumentID] INT IDENTITY (1, 1) NOT NULL, + + -- this is a comment before Title + [Title] NVARCHAR (50) NOT NULL, + + + -- this is a comment before FileName + [FileName] NVARCHAR (400) NOT NULL, + + -- this is a comment before FileExtension + [FileExtension] NVARCHAR(8) +); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_UpperCaseKeywords.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_UpperCaseKeywords.sql new file mode 100644 index 00000000..64740fbc --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_UpperCaseKeywords.sql @@ -0,0 +1,18 @@ + +-- this is a comment before create table +CREATE TABLE [SimpleTable] +( + + -- this is a comment before document + [DocumentID] INT IDENTITY (1, 1) NOT NULL, + + -- this is a comment before Title + [Title] NVARCHAR (50) NOT NULL, + + + -- this is a comment before FileName + [FileName] NVARCHAR (400) NOT NULL, + + -- this is a comment before FileExtension + [FileExtension] nvarchar(8) +); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_UseTabs.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_UseTabs.sql new file mode 100644 index 00000000..0659f45e --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateTable_UseTabs.sql @@ -0,0 +1,18 @@ + +-- this is a comment before create table +CREATE TABLE [SimpleTable] +( + + -- this is a comment before document + [DocumentID] INT IDENTITY (1, 1) NOT NULL, + + -- this is a comment before Title + [Title] NVARCHAR (50) NOT NULL, + + + -- this is a comment before FileName + [FileName] NVARCHAR (400) NOT NULL, + + -- this is a comment before FileExtension + [FileExtension] nvarchar(8) +); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_Full.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_Full.sql new file mode 100644 index 00000000..be912cc7 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_Full.sql @@ -0,0 +1,13 @@ +CREATE VIEW my_schema.my_view_name +( + column1, + column2, + column3 +) +WITH + SCHEMABINDING, + ENCRYPTION, + VIEW_METADATA +AS + SELECT * + FROM mytable diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_FullWithComments.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_FullWithComments.sql new file mode 100644 index 00000000..00194b53 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_FullWithComments.sql @@ -0,0 +1,25 @@ +CREATE VIEW my_schema.my_view_name +--and +( + /* we */ + column1, + column2, + /* can */ + column3 +) +-- put +WITH + /* comments */ + /*even multiple ones */ + -- and of various types + SCHEMABINDING, + -- everywhere + ENCRYPTION, + -- we + VIEW_METADATA +/* want*/ +AS + /* because */ + SELECT * + FROM mytable +-- it's SQL \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_MultipleColumns.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_MultipleColumns.sql new file mode 100644 index 00000000..849df871 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_MultipleColumns.sql @@ -0,0 +1,8 @@ +CREATE VIEW my_view +( + mycol, + my_other_col +) +AS + select * + from mytable diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_MultipleOptions.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_MultipleOptions.sql new file mode 100644 index 00000000..2f0d19d7 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_MultipleOptions.sql @@ -0,0 +1,8 @@ +CREATE VIEW my_view_name +WITH + SCHEMABINDING, + ENCRYPTION, + VIEW_METADATA +AS + SELECT * + FROM mytable diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_OneColumn.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_OneColumn.sql new file mode 100644 index 00000000..6ebf8210 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_OneColumn.sql @@ -0,0 +1,8 @@ + +CREATE VIEW my_view +( + mycol +) +AS + select * + from mytable diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_OneColumnOneOption.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_OneColumnOneOption.sql new file mode 100644 index 00000000..1e349955 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_OneColumnOneOption.sql @@ -0,0 +1,10 @@ +CREATE VIEW my_view +( + mycol +) +WITH + ENCRYPTION +AS + select * + from mytable + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_OneOption.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_OneOption.sql new file mode 100644 index 00000000..fe86fb73 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_OneOption.sql @@ -0,0 +1,8 @@ + +CREATE VIEW my_view +WITH + ENCRYPTION +AS + select * + from mytable + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_Simple.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_Simple.sql new file mode 100644 index 00000000..74bf0c2d --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/CreateView_Simple.sql @@ -0,0 +1,5 @@ +CREATE VIEW my_view +AS + select * + from mytable + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_DefaultValues.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_DefaultValues.sql new file mode 100644 index 00000000..16b7d032 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_DefaultValues.sql @@ -0,0 +1,3 @@ + +INSERT INTO NUMBERS +DEFAULT VALUES; diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_Full.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_Full.sql new file mode 100644 index 00000000..58c0fc85 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_Full.sql @@ -0,0 +1,20 @@ +with + my_initial_table( column1, column2 ) + AS + ( + select * + from mytable + ), + my_other_table( column1, column2 ) + AS + ( + select * + from mytable + ) +Insert top (10) PERCENT +into myserver.mydatabase.myschema.mytable_or_view WITH (TABLOCK) + ( col1, col2, col3, col4, col5 ) +VALUES + ( DEFault, NULL, 1, N'My Value', 'Today'), + ( 45, 5, 1, N'My Last Value', 'Yesterday'), + ( 8, 6, 1, N'My Next Value', 'Tomorrow') \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_OpenQuery.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_OpenQuery.sql new file mode 100644 index 00000000..59e1eafe --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_OpenQuery.sql @@ -0,0 +1,5 @@ + +INSERT OPENQUERY (OracleSvr, 'SELECT name FROM joe.titles') +-- comment +VALUES + ('NewTitle'); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_OutputInto.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_OutputInto.sql new file mode 100644 index 00000000..8beb52b6 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_OutputInto.sql @@ -0,0 +1,8 @@ + +INSERT Production.ScrapReason +OUTPUT + INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate +INTO @MyTableVar +VALUES + (N'Operator error', GETDATE()); + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_OutputStatement.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_OutputStatement.sql new file mode 100644 index 00000000..e62a7329 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_OutputStatement.sql @@ -0,0 +1,8 @@ + +INSERT Production.ScrapReason +/*comments like this one*/ +OUTPUT + INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate +INTO @MyTableVar +VALUES + (N'Operator error',/*comments like this one*/ GETDATE()); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_Select.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_Select.sql new file mode 100644 index 00000000..9cf79922 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_Select.sql @@ -0,0 +1,9 @@ + +INSERT INTO myTable + (FileName, FileType, Document) +SELECT + 'Text1.txt' AS FileName, + '.txt' AS FileType, + * +FROM + OPENROWSET(BULK N'C:\Text1.txt', SINGLE_BLOB) AS Document; \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_SelectSource.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_SelectSource.sql new file mode 100644 index 00000000..9f9aad46 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_SelectSource.sql @@ -0,0 +1,10 @@ + +INSERT INTO myTable + (FileName, FileType, Document) +--comment +SELECT + 'Text1.txt' AS FileName, + '.txt' AS FileType, + * +FROM + OPENROWSET(BULK N'C:\Text1.txt', SINGLE_BLOB) AS Document; diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_TopSpecification.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_TopSpecification.sql new file mode 100644 index 00000000..809f87c2 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_TopSpecification.sql @@ -0,0 +1,6 @@ + +insert top (10) +into mytable +values + (10, 11); + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_TopWithComments.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_TopWithComments.sql new file mode 100644 index 00000000..de21b0b0 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/Insert_TopWithComments.sql @@ -0,0 +1,6 @@ +insert top (10) +mytable +/*comments like this one*//*comments like this one*/ +values + /*comments like this one*/ + (10, 11); \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery.sql new file mode 100644 index 00000000..2bd69b33 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery.sql @@ -0,0 +1,7 @@ + +select c1, c2, t3.c3 + + + + +From t1, t2, t3 \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_20Spaces.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_20Spaces.sql new file mode 100644 index 00000000..5c76ea01 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_20Spaces.sql @@ -0,0 +1,13 @@ + +select + c1, + c2, + t3.c3 + + + + +From + t1, + t2, + t3 \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_CommasBeforeDefinition.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_CommasBeforeDefinition.sql new file mode 100644 index 00000000..aa604a19 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_CommasBeforeDefinition.sql @@ -0,0 +1,7 @@ + +select c1 ,c2 ,t3.c3 + + + + +From t1 ,t2 ,t3 \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_EachReferenceOnNewLine.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_EachReferenceOnNewLine.sql new file mode 100644 index 00000000..a3fe8739 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_EachReferenceOnNewLine.sql @@ -0,0 +1,13 @@ + +select + c1, + c2, + t3.c3 + + + + +From + t1, + t2, + t3 \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_EachReferenceOnNewLine_CommasBeforeDefinition.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_EachReferenceOnNewLine_CommasBeforeDefinition.sql new file mode 100644 index 00000000..3c4ab4a2 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_EachReferenceOnNewLine_CommasBeforeDefinition.sql @@ -0,0 +1,13 @@ + +select + c1 + ,c2 + ,t3.c3 + + + + +From + t1 + ,t2 + ,t3 \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_ForBrowseClause.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_ForBrowseClause.sql new file mode 100644 index 00000000..9ab839b4 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_ForBrowseClause.sql @@ -0,0 +1,9 @@ + +select c1, c2, t3.c3 + + + + +From t1, t2, t3 + +for browse \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_ForXmlClause.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_ForXmlClause.sql new file mode 100644 index 00000000..9da4f29c --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_ForXmlClause.sql @@ -0,0 +1,9 @@ + +select c1, c2, t3.c3 + + + + +From t1, t2, t3 + +FOR XML AUTO, TYPE, XMLSCHEMA, ELEMENTS XSINIL; diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_LowerCaseKeywords.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_LowerCaseKeywords.sql new file mode 100644 index 00000000..254e1a75 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_LowerCaseKeywords.sql @@ -0,0 +1,13 @@ + +select + c1, + c2, + t3.c3 + + + + +from + t1, + t2, + t3 \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_UpperCaseKeywords.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_UpperCaseKeywords.sql new file mode 100644 index 00000000..1e2a0624 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_UpperCaseKeywords.sql @@ -0,0 +1,13 @@ + +SELECT + c1, + c2, + t3.c3 + + + + +FROM + t1, + t2, + t3 \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_UseTabs.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_UseTabs.sql new file mode 100644 index 00000000..2722c411 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/BaselineFiles/SimpleQuery_UseTabs.sql @@ -0,0 +1,13 @@ + +select + c1, + c2, + t3.c3 + + + + +From + t1, + t2, + t3 \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Address.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Address.sql new file mode 100644 index 00000000..cd061941 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Address.sql @@ -0,0 +1,21 @@ +CREATE TABLE [Person].[Address] ( + [AddressID] INT IDENTITY (1, 1) NOT FOR REPLICATION NOT NULL, + CONSTRAINT [PK_Address_AddressID] PRIMARY KEY CLUSTERED ([AddressID] ASC), + + [AddressLine1] NVARCHAR (60) NOT NULL, + + [AddressLine2] NVARCHAR (60) NULL, + + Address NVarChar (60) Null, + + [City] NVARCHAR (30) NOT NULL, + + [StateProvinceID] INT NOT NULL, + + [PostalCode] NVARCHAR (15) NOT NULL, + [rowguid] UNIQUEIDENTIFIER CONSTRAINT [DF_Address_rowguid] DEFAULT (newid()) ROWGUIDCOL NOT NULL, + [ModifiedDate] DATETIME CONSTRAINT [DF_Address_ModifiedDate] DEFAULT (getdate()) NOT NULL, + + + CONSTRAINT [FK_Address_StateProvince_StateProvinceID] FOREIGN KEY ([StateProvinceID]) REFERENCES [Person].[StateProvince] ([StateProvinceID]) ON DELETE NO ACTION ON UPDATE NO ACTION +); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/AddressSimple.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/AddressSimple.sql new file mode 100644 index 00000000..5e6675fd --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/AddressSimple.sql @@ -0,0 +1,5 @@ +CREATE TABLE [Person].[Address] ( + [AddressID] INT IDENTITY (1, 1) NOT FOR REPLICATION NOT NULL, + CONSTRAINT [PK_Address_AddressID] PRIMARY KEY CLUSTERED ([AddressID] ASC),[AddressLine1] NVARCHAR (60) NOT NULL, +); +--closing comment \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/BQE_IndentOperands.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/BQE_IndentOperands.sql new file mode 100644 index 00000000..87ab1289 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/BQE_IndentOperands.sql @@ -0,0 +1,5 @@ +select * +from mytable +intersect +select * +from mytable \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/BQE_KeywordCasing.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/BQE_KeywordCasing.sql new file mode 100644 index 00000000..1967b68f --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/BQE_KeywordCasing.sql @@ -0,0 +1,15 @@ + + seLect * + from mytable +unIon + selECT * + fROM mytable +union ALL + select * + from mytable +excepT + select * + from mytable +Intersect + select * + from mytable \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/BQE_OperatorsOnNewLine.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/BQE_OperatorsOnNewLine.sql new file mode 100644 index 00000000..847da065 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/BQE_OperatorsOnNewLine.sql @@ -0,0 +1 @@ +select * from mytable union select * from mytable except select * from mytable union all select * from mytable intersect select * from mytable \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CTE.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CTE.sql new file mode 100644 index 00000000..7a33693b --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CTE.sql @@ -0,0 +1,18 @@ +USE AdventureWorks2008R2; +GO +-- Define the CTE expression name and column list. +WITH Sales_CTE (SalesPersonID, SalesOrderID, SalesYear) +AS +-- Define the CTE query. +( + SELECT SalesPersonID, SalesOrderID, YEAR(OrderDate) AS SalesYear + FROM Sales.SalesOrderHeader + WHERE SalesPersonID IS NOT NULL +) +-- Define the outer query referencing the CTE name. +SELECT SalesPersonID, COUNT(SalesOrderID) AS TotalSales, SalesYear +FROM Sales_CTE +GROUP BY SalesYear, SalesPersonID +ORDER BY SalesPersonID, SalesYear; +GO + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CTE_MultipleExpressions.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CTE_MultipleExpressions.sql new file mode 100644 index 00000000..5fb85014 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CTE_MultipleExpressions.sql @@ -0,0 +1 @@ +with my_initial_table( column1, column2 ) AS ( select * from mytable ), my_other_table( column1X, column2X ) AS ( select * from mytable2 ) select distinct top (10) PERCENT with ties alias1 = SIZE(mytable.mycol1), another = COUNT(new_elements), count(money) AS encore, id INTO my_new_table FROM my_initial_table \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CTE_OneColumn.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CTE_OneColumn.sql new file mode 100644 index 00000000..d26d33c7 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CTE_OneColumn.sql @@ -0,0 +1,16 @@ +USE AdventureWorks2008R2; +GO +-- Define the CTE expression name and column list. +WITH Sales_CTE (SalesOrderID) +AS +-- Define the CTE query. +( + SELECT SalesOrderID + FROM Sales.SalesOrderHeader + WHERE SalesPersonID IS NOT NULL +) +-- Define the outer query referencing the CTE name. +SELECT COUNT(SalesOrderID) AS TotalSales +FROM Sales_CTE; +GO + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure.sql new file mode 100644 index 00000000..88dd73b1 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure.sql @@ -0,0 +1,12 @@ +CREATE PROCEDURE au_info + @lastname varchar(40), + @firstname varchar(20) +AS +SELECT au_lname, au_fname, title, pub_name + FROM authors a INNER JOIN titleauthor ta + ON a.au_id = ta.au_id INNER JOIN titles t + ON t.title_id = ta.title_id INNER JOIN publishers p + ON t.pub_id = p.pub_id + WHERE au_fname = @firstname + AND au_lname = @lastname +GO diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_BackwardsCompatible.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_BackwardsCompatible.sql new file mode 100644 index 00000000..3129d11a Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_BackwardsCompatible.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_BeginEnd.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_BeginEnd.sql new file mode 100644 index 00000000..d78b2346 Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_BeginEnd.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_Minimal.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_Minimal.sql new file mode 100644 index 00000000..00cf829c --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_Minimal.sql @@ -0,0 +1,3 @@ + + -- my comment +Create Procedure P1 AS \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_MultipleBatches.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_MultipleBatches.sql new file mode 100644 index 00000000..871678f0 Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_MultipleBatches.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_MultipleParams.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_MultipleParams.sql new file mode 100644 index 00000000..8badc9b8 Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_MultipleParams.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_OneParam.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_OneParam.sql new file mode 100644 index 00000000..f5de7101 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_OneParam.sql @@ -0,0 +1 @@ +Create Procedure dbo.P1 @param1 int AS \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_ParamsRecompileReturn.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_ParamsRecompileReturn.sql new file mode 100644 index 00000000..e7d85407 Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_ParamsRecompileReturn.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_Select.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_Select.sql new file mode 100644 index 00000000..420d5d07 Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_Select.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_TwoPartName.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_TwoPartName.sql new file mode 100644 index 00000000..80515c95 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_TwoPartName.sql @@ -0,0 +1 @@ +Create Procedure dbo.P1 AS \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_WithCTE.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_WithCTE.sql new file mode 100644 index 00000000..7b072ce8 Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_WithCTE.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_WithEncryptionModule.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_WithEncryptionModule.sql new file mode 100644 index 00000000..a0c65c2d Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_WithEncryptionModule.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_WithExecuteAsModule.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_WithExecuteAsModule.sql new file mode 100644 index 00000000..ad09da9e Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_WithExecuteAsModule.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_WithThreeModules.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_WithThreeModules.sql new file mode 100644 index 00000000..43770414 Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateProcedure_WithThreeModules.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateTable.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateTable.sql new file mode 100644 index 00000000..580d6ac5 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateTable.sql @@ -0,0 +1,17 @@ + + -- this is a comment before create table +CREATE TABLE [SimpleTable] ( + + -- this is a comment before document + [DocumentID] INT IDENTITY (1, 1) NOT NULL, + + -- this is a comment before Title +[Title] NVARCHAR (50) NOT NULL, + + + -- this is a comment before FileName + [FileName] NVARCHAR (400) NOT NULL, + + -- this is a comment before FileExtension + [FileExtension] nvarchar(8) +); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateTableFull.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateTableFull.sql new file mode 100644 index 00000000..17ee81e8 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateTableFull.sql @@ -0,0 +1,10 @@ +CREATE TABLE + db_name.schema_name.table_name + ( + col_name1 INT, + col_name2 VARCHAR (20) + + +-- here is my comment +) + ON partition_schema_name (partition_column_name); \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateTable_CommentBeforeComma.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateTable_CommentBeforeComma.sql new file mode 100644 index 00000000..ee2ed324 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateTable_CommentBeforeComma.sql @@ -0,0 +1,9 @@ + +create table t1 +( + id INT not null /* awesome */, x INT not null, /* cool */ y INT not null -- tremendeous +) +with +( + data_compression = row +); \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateTable_Formatted.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateTable_Formatted.sql new file mode 100644 index 00000000..e16e281d --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateTable_Formatted.sql @@ -0,0 +1,7 @@ + +create table t1 +( + col1 int, + + col2 int +) \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateTable_Timestamp.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateTable_Timestamp.sql new file mode 100644 index 00000000..32493073 Binary files /dev/null and b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateTable_Timestamp.sql differ diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_Full.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_Full.sql new file mode 100644 index 00000000..25ae8eb9 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_Full.sql @@ -0,0 +1,6 @@ +CREATE VIEW my_schema.my_view_name +(column1, column2, + column3 +) +WITH SCHEMABINDING, ENCRYPTION,VIEW_METADATA +AS SELECT * FROM mytable diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_FullWithComments.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_FullWithComments.sql new file mode 100644 index 00000000..d952d0a6 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_FullWithComments.sql @@ -0,0 +1,11 @@ +CREATE VIEW my_schema.my_view_name --and +( /* we */ column1, column2, /* can */ column3 +) -- put +WITH /* comments */ /*even multiple ones */ -- and of various types + SCHEMABINDING, -- everywhere + ENCRYPTION, +-- we + VIEW_METADATA /* want*/ +AS /* because */ + SELECT * FROM mytable +-- it's SQL \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_MultipleColumns.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_MultipleColumns.sql new file mode 100644 index 00000000..1d29a253 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_MultipleColumns.sql @@ -0,0 +1,3 @@ +CREATE VIEW my_view (mycol, my_other_col) +AS + select * from mytable diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_MultipleOptions.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_MultipleOptions.sql new file mode 100644 index 00000000..b1647f1b --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_MultipleOptions.sql @@ -0,0 +1,4 @@ +CREATE VIEW my_view_name +WITH SCHEMABINDING, ENCRYPTION, VIEW_METADATA +AS +SELECT * FROM mytable diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_OneColumn.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_OneColumn.sql new file mode 100644 index 00000000..b1ee1ff9 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_OneColumn.sql @@ -0,0 +1,4 @@ + +CREATE VIEW my_view (mycol) +AS + select * from mytable diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_OneColumnOneOption.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_OneColumnOneOption.sql new file mode 100644 index 00000000..49fdf21f --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_OneColumnOneOption.sql @@ -0,0 +1,4 @@ +CREATE VIEW my_view (mycol) WITH ENCRYPTION +AS + select * from mytable + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_OneOption.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_OneOption.sql new file mode 100644 index 00000000..5f5c6524 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_OneOption.sql @@ -0,0 +1,5 @@ + +CREATE VIEW my_view WITH ENCRYPTION +AS + select * from mytable + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_Simple.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_Simple.sql new file mode 100644 index 00000000..331874f3 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/CreateView_Simple.sql @@ -0,0 +1,4 @@ +CREATE VIEW my_view +AS + select * from mytable + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_DefaultValues.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_DefaultValues.sql new file mode 100644 index 00000000..e48e2969 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_DefaultValues.sql @@ -0,0 +1,2 @@ + + INSERT INTO NUMBERS DEFAULT VALUES; diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_Full.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_Full.sql new file mode 100644 index 00000000..bc6729aa --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_Full.sql @@ -0,0 +1 @@ +with my_initial_table( column1, column2 )AS ( select * from mytable ), my_other_table( column1, column2 ) AS ( select * from mytable ) Insert top (10) PERCENT into myserver.mydatabase.myschema.mytable_or_view WITH (TABLOCK)( col1, col2, col3, col4, col5 ) VALUES ( DEFault, NULL, 1, N'My Value', 'Today'),( 45, 5, 1, N'My Last Value', 'Yesterday'),( 8, 6, 1, N'My Next Value', 'Tomorrow') \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_OpenQuery.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_OpenQuery.sql new file mode 100644 index 00000000..99427306 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_OpenQuery.sql @@ -0,0 +1,3 @@ + +INSERT OPENQUERY (OracleSvr, 'SELECT name FROM joe.titles')-- comment +VALUES ('NewTitle'); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_OutputInto.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_OutputInto.sql new file mode 100644 index 00000000..ec4826fd --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_OutputInto.sql @@ -0,0 +1,5 @@ + +INSERT Production.ScrapReason OUTPUT + INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate +INTO @MyTableVar VALUES (N'Operator error', GETDATE()); + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_OutputStatement.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_OutputStatement.sql new file mode 100644 index 00000000..d9289344 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_OutputStatement.sql @@ -0,0 +1,4 @@ + +INSERT Production.ScrapReason /*comments like this one*/ OUTPUT + INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate +INTO @MyTableVar VALUES (N'Operator error',/*comments like this one*/ GETDATE()); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_Select.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_Select.sql new file mode 100644 index 00000000..324bda12 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_Select.sql @@ -0,0 +1,2 @@ + +INSERT INTO myTable(FileName, FileType, Document) SELECT 'Text1.txt' AS FileName, '.txt' AS FileType, * FROM OPENROWSET(BULK N'C:\Text1.txt', SINGLE_BLOB) AS Document; \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_SelectSource.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_SelectSource.sql new file mode 100644 index 00000000..1ba6ada2 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_SelectSource.sql @@ -0,0 +1,4 @@ + +INSERT INTO myTable(FileName, FileType, Document) --comment + SELECT 'Text1.txt' AS FileName, '.txt' AS FileType, + * FROM OPENROWSET(BULK N'C:\Text1.txt', SINGLE_BLOB) AS Document; diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_TopSpecification.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_TopSpecification.sql new file mode 100644 index 00000000..88f0756f --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_TopSpecification.sql @@ -0,0 +1,3 @@ + +insert top (10) into mytable values (10, 11); + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_TopWithComments.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_TopWithComments.sql new file mode 100644 index 00000000..10bb8c2b --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/Insert_TopWithComments.sql @@ -0,0 +1 @@ +insert top (10) mytable /*comments like this one*//*comments like this one*/ values /*comments like this one*/ (10, 11); \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/NestedSelect.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/NestedSelect.sql new file mode 100644 index 00000000..c223cd26 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/NestedSelect.sql @@ -0,0 +1,13 @@ +-- number of tests in number of failed tests in control queue +select (select count(TestResults.TestName) + from TestResults + JOIN Jobs ON (TestResults.JobPath = Jobs.JobPath) + JOIN SnapSubmissions on (SnapSubmissions.JobsPath = jobs.SubmissionParent) + where SnapSubmissions.SnapQueueName = 'SqlStudio_control' + AND TestResults.Outcome = 'Failed') as FailedTestsInControlQueue, + (select count(TestResults.TestName) + from TestResults + JOIN Jobs ON (TestResults.JobPath = Jobs.JobPath) + JOIN SnapSubmissions on (SnapSubmissions.JobsPath = jobs.SubmissionParent) + where SnapSubmissions.SnapQueueName = 'SqlStudio_control') as NumberOfTestsInControlQueue + -- (FailedTestsInControlQueue / NumberOfTestsInControlQueue) as PercentageOfTestsFailed \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/SelectWithColumnAlias.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/SelectWithColumnAlias.sql new file mode 100644 index 00000000..6d501d31 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/SelectWithColumnAlias.sql @@ -0,0 +1,10 @@ +USE AdventureWorks2008R2; +GO +SELECT p.Name AS ProductName, +NonDiscountSales = (OrderQty * UnitPrice), +Discounts = ((OrderQty * UnitPrice) * UnitPriceDiscount) +FROM Production.Product AS p +INNER JOIN Sales.SalesOrderDetail AS sod +ON p.ProductID = sod.ProductID +ORDER BY ProductName DESC; +GO diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/SimpleQuery.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/SimpleQuery.sql new file mode 100644 index 00000000..fdbe04f5 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/SimpleQuery.sql @@ -0,0 +1,7 @@ + +select c1, c2,t3.c3 + + + + +From t1, t2,t3 \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/SimpleQuery_ForBrowseClause.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/SimpleQuery_ForBrowseClause.sql new file mode 100644 index 00000000..a424acd2 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/SimpleQuery_ForBrowseClause.sql @@ -0,0 +1,9 @@ + +select c1, c2,t3.c3 + + + + +From t1, t2,t3 + +for browse \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/SimpleQuery_ForXmlClause.sql b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/SimpleQuery_ForXmlClause.sql new file mode 100644 index 00000000..ed665612 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestData/TSqlFormatter/TestFiles/SimpleQuery_ForXmlClause.sql @@ -0,0 +1,9 @@ + +select c1, c2,t3.c3 + + + + +From t1, t2,t3 + +FOR XML AUTO, TYPE, XMLSCHEMA, ELEMENTS XSINIL; diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestServiceProvider.cs b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestServiceProvider.cs index c92c5261..1ed51b65 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestServiceProvider.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestServiceProvider.cs @@ -11,7 +11,6 @@ using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.Credentials; using Microsoft.SqlTools.ServiceLayer.Hosting; -using Microsoft.SqlTools.ServiceLayer.LanguageServices; using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage; using Microsoft.SqlTools.ServiceLayer.SqlContext; @@ -145,19 +144,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Common SqlToolsContext sqlToolsContext = new SqlToolsContext(hostDetails); // Grab the instance of the service host - ServiceHost serviceHost = ServiceHost.Instance; - - // Start the service - serviceHost.Start().Wait(); - - // Initialize the services that will be hosted here - WorkspaceService.Instance.InitializeService(serviceHost); - LanguageService.Instance.InitializeService(serviceHost, sqlToolsContext); - ConnectionService.Instance.InitializeService(serviceHost); - CredentialService.Instance.InitializeService(serviceHost); - QueryExecutionService.Instance.InitializeService(serviceHost); - - serviceHost.Initialize(); + ServiceHost serviceHost = HostLoader.CreateAndStartServiceHost(sqlToolsContext); } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestUtilities.cs b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestUtilities.cs new file mode 100644 index 00000000..695be588 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/TestUtilities.cs @@ -0,0 +1,90 @@ +// +// 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; + +namespace Microsoft.SqlTools.ServiceLayer.Test.Common +{ + public class TestUtilities + { + + public static void CompareTestFiles(FileInfo baselinePath, FileInfo outputPath, int maxDiffLines = -1 /* unlimited */) + { + if (!baselinePath.Exists) + { + throw new ComparisonFailureException("echo Test Failed: Baseline file " + baselinePath.FullName + " does not exist" + + Environment.NewLine + Environment.NewLine + "echo test > \"" + baselinePath.FullName + "\""); + } + + if (!outputPath.Exists) + { + throw new ComparisonFailureException("Test Failed: output file " + outputPath.FullName + " doesn't exist."); + } + + string baseline = ReadTextAndNormalizeLineEndings(baselinePath.FullName); + string actual = ReadTextAndNormalizeLineEndings(outputPath.FullName); + + if (baseline.CompareTo(actual) != 0) + { + string header = "Test Failed: Baseline file " + baselinePath.FullName + " differs from output file " + outputPath.FullName + "\r\n\r\n"; + string editAndCopyMessage = + "\r\n\r\n copy \"" + outputPath.FullName + "\" \"" + baselinePath.FullName + "\"" + + "\r\n\r\n"; + string diffCmdMessage = + "code --diff \"" + baselinePath.FullName + "\" \"" + outputPath.FullName + "\"" + + "\r\n\r\n"; + + string diffContents = FindFirstDifference(baseline, actual); + throw new ComparisonFailureException(header + diffCmdMessage + editAndCopyMessage + diffContents, editAndCopyMessage); + } + } + + + private static string FindFirstDifference(string baseline, string actual) + { + int index = 0; + int minEnd = Math.Min(baseline.Length, actual.Length); + while (index < minEnd && baseline[index] == actual[index]) + index++; + + int firstDiffIndex = (index == minEnd && baseline.Length == actual.Length) ? -1 : index; + + int startRange = Math.Max(firstDiffIndex - 50, 0); + int endRange = Math.Min(firstDiffIndex + 50, minEnd); + + string baselineDiff = ShowWhitespace(baseline.Substring(startRange, endRange)); + string actualDiff = ShowWhitespace(actual.Substring(startRange, endRange)); + return "\r\nFirst Diff:\r\n===== Baseline =====\r\n" + + baselineDiff + + "\r\n===== Actual =====\r\n" + + actualDiff; + } + + private static string ShowWhitespace(string input) + { + return input.Replace("\r", "\\r").Replace("\n", "\\n"); + } + + /// + /// Normalizes line endings in a file to facilitate comparisons regardless of OS. On Windows line endings are \r\n, while + /// on other systems only \n is used + /// + public static string ReadTextAndNormalizeLineEndings(string filePath) + { + string text = File.ReadAllText(filePath); + return NormalizeLineEndings(text); + } + + public static string NormalizeLineEndings(string text) + { + // To work on all platforms, we first stript \r and then replace any remaining \n characters + // with a newline string. This helps keep things consistent with file formats for each OS + string noCrs = text.Replace("\r", ""); + return noCrs.Replace("\n", Environment.NewLine); + } + + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/CredentialServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/CredentialServiceTests.cs index d174c48e..d49cfa75 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/CredentialServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Credentials/CredentialServiceTests.cs @@ -81,7 +81,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection var contextMock = RequestContextMocks.Create(null).AddErrorHandling(obj => errorResponse = obj); await service.HandleSaveCredentialRequest(new Credential(null), contextMock.Object); - VerifyErrorSent(contextMock); + TestUtils.VerifyErrorSent(contextMock); Assert.True(((string)errorResponse).Contains("ArgumentException")); } @@ -92,14 +92,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection var contextMock = RequestContextMocks.Create(null).AddErrorHandling(obj => errorResponse = obj); await service.HandleSaveCredentialRequest(new Credential(credentialId), contextMock.Object); - VerifyErrorSent(contextMock); + TestUtils.VerifyErrorSent(contextMock); Assert.True(((string)errorResponse).Contains("ArgumentException")); } [Fact] public async Task SaveCredentialWorksForSingleCredential() { - await RunAndVerify( + await TestUtils.RunAndVerify( test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), verify: (actual => Assert.True(actual))); } @@ -107,11 +107,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection [Fact] public async Task SaveCredentialSupportsSavingCredentialMultipleTimes() { - await RunAndVerify( + await TestUtils.RunAndVerify( test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), verify: (actual => Assert.True(actual))); - await RunAndVerify( + await TestUtils.RunAndVerify( test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), verify: (actual => Assert.True(actual))); } @@ -120,13 +120,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection public async Task ReadCredentialWorksForSingleCredential() { // Given we have saved the credential - await RunAndVerify( + await TestUtils.RunAndVerify( test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), verify: (actual => Assert.True(actual, "Expect Credential to be saved successfully"))); // Expect read of the credential to return the password - await RunAndVerify( + await TestUtils.RunAndVerify( test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credentialId, null), requestContext), verify: (actual => { @@ -139,22 +139,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection { // Given we have saved multiple credentials - await RunAndVerify( + await TestUtils.RunAndVerify( test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), verify: (actual => Assert.True(actual, "Expect Credential to be saved successfully"))); - await RunAndVerify( + await TestUtils.RunAndVerify( test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(otherCredId, otherPassword), requestContext), verify: (actual => Assert.True(actual, "Expect Credential to be saved successfully"))); // Expect read of the credentials to return the right password - await RunAndVerify( + await TestUtils.RunAndVerify( test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credentialId, null), requestContext), verify: (actual => { Assert.Equal(password1, actual.Password); })); - await RunAndVerify( + await TestUtils.RunAndVerify( test: (requestContext) => service.HandleReadCredentialRequest(new Credential(otherCredId, null), requestContext), verify: (actual => { @@ -166,17 +166,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection public async Task ReadCredentialHandlesPasswordUpdate() { // Given we have saved twice with a different password - await RunAndVerify( + await TestUtils.RunAndVerify( test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), verify: (actual => Assert.True(actual))); - await RunAndVerify( + await TestUtils.RunAndVerify( test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password2), requestContext), verify: (actual => Assert.True(actual))); // When we read the value for this credential // Then we expect only the last saved password to be found - await RunAndVerify( + await TestUtils.RunAndVerify( test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credentialId), requestContext), verify: (actual => { @@ -192,7 +192,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection // Verify throws on null, and this is sent as an error await service.HandleReadCredentialRequest(null, contextMock.Object); - VerifyErrorSent(contextMock); + TestUtils.VerifyErrorSent(contextMock); Assert.True(((string)errorResponse).Contains("ArgumentNullException")); } @@ -204,7 +204,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection // Verify throws with no ID await service.HandleReadCredentialRequest(new Credential(), contextMock.Object); - VerifyErrorSent(contextMock); + TestUtils.VerifyErrorSent(contextMock); Assert.True(((string)errorResponse).Contains("ArgumentException")); } @@ -216,7 +216,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection // When reading the credential // Then expect the credential to be returned but password left blank - await RunAndVerify( + await TestUtils.RunAndVerify( test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credWithNoPassword, null), requestContext), verify: (actual => { @@ -234,7 +234,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection // Verify throws with no ID await service.HandleDeleteCredentialRequest(new Credential(), contextMock.Object); - VerifyErrorSent(contextMock); + TestUtils.VerifyErrorSent(contextMock); Assert.True(((string)errorResponse).Contains("ArgumentException")); } @@ -242,49 +242,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection public async Task DeleteCredentialReturnsTrueOnlyIfCredentialExisted() { // Save should be true - await RunAndVerify( + await TestUtils.RunAndVerify( test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), verify: (actual => Assert.True(actual))); // Then delete - should return true - await RunAndVerify( + await TestUtils.RunAndVerify( test: (requestContext) => service.HandleDeleteCredentialRequest(new Credential(credentialId), requestContext), verify: (actual => Assert.True(actual))); // Then delete - should return false as no longer exists - await RunAndVerify( + await TestUtils.RunAndVerify( test: (requestContext) => service.HandleDeleteCredentialRequest(new Credential(credentialId), requestContext), verify: (actual => Assert.False(actual))); } - private async Task RunAndVerify(Func, Task> test, Action verify) - { - T result = default(T); - var contextMock = RequestContextMocks.Create(r => result = r).AddErrorHandling(null); - await test(contextMock.Object); - VerifyResult(contextMock, verify, result); - } - - private void VerifyErrorSent(Mock> contextMock) - { - contextMock.Verify(c => c.SendResult(It.IsAny()), Times.Never); - contextMock.Verify(c => c.SendError(It.IsAny()), Times.Once); - } - - private void VerifyResult(Mock> contextMock, U expected, U actual) - { - contextMock.Verify(c => c.SendResult(It.IsAny()), Times.Once); - Assert.Equal(expected, actual); - contextMock.Verify(c => c.SendError(It.IsAny()), Times.Never); - } - - private void VerifyResult(Mock> contextMock, Action verify, T actual) - { - contextMock.Verify(c => c.SendResult(It.IsAny()), Times.Once); - contextMock.Verify(c => c.SendError(It.IsAny()), Times.Never); - verify(actual); - } - } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Extensibility/ExtensionTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Extensibility/ExtensionTests.cs new file mode 100644 index 00000000..009c0531 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Extensibility/ExtensionTests.cs @@ -0,0 +1,88 @@ +// +// 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.Composition; +using System.Linq; +using System.Reflection; +using Microsoft.SqlTools.ServiceLayer.Extensibility; +using Microsoft.SqlTools.ServiceLayer.Formatter; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.Test.Extensibility +{ + public class ExtensionTests + { + + [Fact] + public void CreateAssemblyStoreShouldFindTypesInAssembly() + { + // Given a store for MyExportType + ExtensionStore store = ExtensionStore.CreateAssemblyStore(GetType().GetTypeInfo().Assembly); + // Then should get any export for this type and subtypes + Assert.Equal(2, store.GetExports().Count()); + + // But for a different type, expect throw as the store only contains MyExportType + Assert.Throws(() => store.GetExports().Count()); + } + + [Fact] + public void CreateDefaultLoaderShouldOnlyFindTypesInMainAssembly() + { + // Given a store created using CreateDefaultLoader + // Then should not find exports from a different assembly + ExtensionStore store = ExtensionStore.CreateDefaultLoader(); + Assert.Equal(0, store.GetExports().Count()); + + // But should find exports that are defined in the main assembly + store = ExtensionStore.CreateDefaultLoader(); + Assert.NotEmpty(store.GetExports()); + } + + + [Fact] + public void CreateDefaultServiceProviderShouldOnlyFindTypesInMainAssembly() + { + // Given a default ExtensionServiceProvider + // Then should not find exports from a different assembly + ExtensionServiceProvider serviceProvider = ExtensionServiceProvider.CreateDefaultServiceProvider(); + Assert.Empty(serviceProvider.GetServices()); + + // But should find exports that are defined in the main assembly + Assert.NotEmpty(serviceProvider.GetServices()); + } + + [Fact] + public void CreateStoreForCurrentDirectoryShouldFindExportsInDirectory() + { + // Given stores created for types in different assemblies + ExtensionStore myStore = ExtensionStore.CreateStoreForCurrentDirectory(); + ExtensionStore querierStore = ExtensionStore.CreateStoreForCurrentDirectory(); + + // When I query exports + // Then exports for all assemblies should be found + Assert.Equal(2, myStore.GetExports().Count()); + Assert.NotEmpty(querierStore.GetExports()); + } + } + + // Note: in order for the MEF lookup to succeed, one class must have + [Export(typeof(MyExportType))] + public class MyExportType + { + + } + + public class MyExportSubType : MyExportType + { + + } + + public class MyOtherType + { + + } +} + diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Extensibility/ServiceProviderTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Extensibility/ServiceProviderTests.cs new file mode 100644 index 00000000..1596bd21 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Extensibility/ServiceProviderTests.cs @@ -0,0 +1,115 @@ +// +// 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.Linq; +using Microsoft.SqlTools.ServiceLayer.Extensibility; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.Test.Extensibility +{ + public class ServiceProviderTests + { + private RegisteredServiceProvider provider; + public ServiceProviderTests() + { + provider = new RegisteredServiceProvider(); + } + + [Fact] + public void GetServiceShouldReturnNullIfNoServicesRegistered() + { + // Given no service registered + // When I call GetService + var service = provider.GetService(); + // Then I expect null to be returned + Assert.Null(service); + } + + + [Fact] + public void GetSingleServiceThrowsMultipleServicesRegistered() + { + // Given 2 services registered + provider.Register(() => new[] { new MyProviderService(), new MyProviderService() }); + // When I call GetService + // Then I expect to throw + Assert.Throws(() => provider.GetService()); + } + + [Fact] + public void GetServicesShouldReturnEmptyIfNoServicesRegistered() + { + // Given no service regisstered + // When I call GetService + var services = provider.GetServices(); + // Then I expect empty enumerable to be returned + Assert.NotNull(services); + Assert.Equal(0, services.Count()); + } + + [Fact] + public void GetServiceShouldReturnRegisteredService() + { + MyProviderService service = new MyProviderService(); + provider.RegisterSingleService(service); + + var returnedService = provider.GetService(); + Assert.Equal(service, returnedService); + } + + [Fact] + public void GetServicesShouldReturnRegisteredServiceWhenMultipleServicesRegistered() + { + MyProviderService service = new MyProviderService(); + provider.RegisterSingleService(service); + + var returnedServices = provider.GetServices(); + Assert.Equal(service, returnedServices.Single()); + } + + [Fact] + public void RegisterServiceProviderShouldThrowIfServiceIsIncompatible() + { + MyProviderService service = new MyProviderService(); + Assert.Throws(() => provider.RegisterSingleService(typeof(OtherService), service)); + } + [Fact] + public void RegisterServiceProviderShouldThrowIfServiceAlreadyRegistered() + { + MyProviderService service = new MyProviderService(); + provider.RegisterSingleService(service); + + Assert.Throws(() => provider.RegisterSingleService(service)); + } + + [Fact] + public void RegisterShouldThrowIfServiceAlreadyRegistered() + { + MyProviderService service = new MyProviderService(); + provider.RegisterSingleService(service); + + Assert.Throws(() => provider.Register(() => service.SingleItemAsEnumerable())); + } + + [Fact] + public void RegisterShouldThrowIfServicesAlreadyRegistered() + { + provider.Register(() => new [] { new MyProviderService(), new MyProviderService() }); + Assert.Throws(() => provider.Register(() => new MyProviderService().SingleItemAsEnumerable())); + } + } + + + public class MyProviderService + { + + } + + public class OtherService + { + + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/BinaryQueryExpressionFormatterTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/BinaryQueryExpressionFormatterTests.cs new file mode 100644 index 00000000..0639b01f --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/BinaryQueryExpressionFormatterTests.cs @@ -0,0 +1,57 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.ServiceLayer.Formatter; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.Test.Formatter +{ + public class BinaryQueryExpressionFormatterTests : FormatterUnitTestsBase + { + [Fact] + public void BQE_IndentOperands() + { + FormatOptions options = new FormatOptions(); + //options.PlaceEachReferenceOnNewLineInQueryStatements = true; + LoadAndFormatAndCompare("BQE_IndentOperands", GetInputFile("BQE_IndentOperands.sql"), + GetBaselineFile("BQE_IndentOperands.sql"), options, true); + } + + [Fact] + public void BQE_KeywordCasing_UpperCase() + { + FormatOptions options = new FormatOptions(); + options.KeywordCasing = CasingOptions.Uppercase; + LoadAndFormatAndCompare("BQE_KeywordCasing_UpperCase", GetInputFile("BQE_KeywordCasing.sql"), + GetBaselineFile("BQE_KeywordCasing_UpperCase.sql"), options, true); + } + + [Fact] + public void BQE_KeywordCasing_LowerCase() + { + FormatOptions options = new FormatOptions(); + options.KeywordCasing = CasingOptions.Lowercase; + LoadAndFormatAndCompare("BQE_KeywordCasing_LowerCase", GetInputFile("BQE_KeywordCasing.sql"), + GetBaselineFile("BQE_KeywordCasing_LowerCase.sql"), options, true); + } + + [Fact] + public void BQE_KeywordCasing_NoFormat() + { + FormatOptions options = new FormatOptions(); + options.KeywordCasing = CasingOptions.None; + LoadAndFormatAndCompare("BQE_KeywordCasing_NoFormat", GetInputFile("BQE_KeywordCasing.sql"), + GetBaselineFile("BQE_KeywordCasing_NoFormat.sql"), options, true); + } + + [Fact] + public void BQE_OperatorsOnNewLine() + { + FormatOptions options = new FormatOptions(); + LoadAndFormatAndCompare("BQE_OperatorsOnNewLine", GetInputFile("BQE_OperatorsOnNewLine.sql"), + GetBaselineFile("BQE_OperatorsOnNewLine.sql"), options, true); + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/CommonTableExpressionFormatterTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/CommonTableExpressionFormatterTests.cs new file mode 100644 index 00000000..eee819f9 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/CommonTableExpressionFormatterTests.cs @@ -0,0 +1,110 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + + +using Microsoft.SqlTools.ServiceLayer.Formatter; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.Test.Formatter +{ + + public class CommonTableExpressionFormatterTests : FormatterUnitTestsBase + { + [Fact] + public void CTE() + { + LoadAndFormatAndCompare("CTE", GetInputFile("CTE.sql"), + GetBaselineFile("CTE.sql"), new FormatOptions(), true); + } + + [Fact] + public void CTE_OneColumn() + { + LoadAndFormatAndCompare("CTE_OneColumn", GetInputFile("CTE_OneColumn.sql"), + GetBaselineFile("CTE_OneColumn.sql"), new FormatOptions(), true); + } + + [Fact] + public void CTE_MultipleExpressions() + { + LoadAndFormatAndCompare("CTE_MultipleExpressions", GetInputFile("CTE_MultipleExpressions.sql"), + GetBaselineFile("CTE_MultipleExpressions.sql"), new FormatOptions(), true); + } + + [Fact] + public void CTE_CommasBeforeDefinition() + { + FormatOptions options = new FormatOptions(); + options.PlaceCommasBeforeNextStatement = true; + + // TODO: fix verify to account for commma placement - this can + LoadAndFormatAndCompare("CTE_CommasBeforeDefinition", GetInputFile("CTE.sql"), + GetBaselineFile("CTE_CommasBeforeDefinition.sql"), options, false); + } + + [Fact] + public void CTE_EachReferenceOnNewLine() + { + FormatOptions options = new FormatOptions(); + options.PlaceEachReferenceOnNewLineInQueryStatements = true; + + LoadAndFormatAndCompare("CTE_EachReferenceOnNewLine", GetInputFile("CTE.sql"), + GetBaselineFile("CTE_EachReferenceOnNewLine.sql"), options, true); + } + + [Fact] + public void CTE_EachReferenceOnNewLine_CommasBeforeDefinition() + { + FormatOptions options = new FormatOptions(); + options.PlaceCommasBeforeNextStatement = true; + options.PlaceEachReferenceOnNewLineInQueryStatements = true; + + // TODO: fix verify to account for commma placement - this can + LoadAndFormatAndCompare("CTE_EachReferenceOnNewLine_CommasBeforeDefinition", GetInputFile("CTE.sql"), + GetBaselineFile("CTE_EachReferenceOnNewLine_CommasBeforeDefinition.sql"), options, false); + } + + [Fact] + public void CTE_UseTabs() + { + FormatOptions options = new FormatOptions(); + options.UseTabs = true; + options.PlaceEachReferenceOnNewLineInQueryStatements = true; + LoadAndFormatAndCompare("CTE_UseTabs", GetInputFile("CTE.sql"), + GetBaselineFile("CTE_UseTabs.sql"), options, true); + } + + [Fact] + public void CTE_20Spaces() + { + FormatOptions options = new FormatOptions(); + options.SpacesPerIndent = 20; + options.PlaceEachReferenceOnNewLineInQueryStatements = true; + LoadAndFormatAndCompare("CTE_20Spaces", GetInputFile("CTE.sql"), + GetBaselineFile("CTE_20Spaces.sql"), options, true); + } + + [Fact] + public void CTE_UpperCaseKeywords() + { + FormatOptions options = new FormatOptions(); + options.KeywordCasing = CasingOptions.Uppercase; + options.PlaceEachReferenceOnNewLineInQueryStatements = true; + LoadAndFormatAndCompare("CTE_UpperCaseKeywords", GetInputFile("CTE.sql"), + GetBaselineFile("CTE_UpperCaseKeywords.sql"), options, true); + } + + [Fact] + public void CTE_LowerCaseKeywords() + { + FormatOptions options = new FormatOptions(); + options.KeywordCasing = CasingOptions.Lowercase; + options.PlaceEachReferenceOnNewLineInQueryStatements = true; + LoadAndFormatAndCompare("CTE_LowerCaseKeywords", GetInputFile("CTE.sql"), + GetBaselineFile("CTE_LowerCaseKeywords.sql"), options, true); + } + + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/CreateProcedureFormatterTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/CreateProcedureFormatterTests.cs new file mode 100644 index 00000000..1870e41b --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/CreateProcedureFormatterTests.cs @@ -0,0 +1,104 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.ServiceLayer.Formatter; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.Test.Formatter +{ + + public class CreateProcedureFormatterTests : FormatterUnitTestsBase + { + [Fact] + public void CreateProcedure_BackwardsCompatible() + { + LoadAndFormatAndCompare("CreateProcedure_BackwardsCompatible", GetInputFile("CreateProcedure_BackwardsCompatible.sql"), + GetBaselineFile("CreateProcedure_BackwardsCompatible.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateProcedure_BeginEnd() + { + LoadAndFormatAndCompare("CreateProcedure_BeginEnd", GetInputFile("CreateProcedure_BeginEnd.sql"), + GetBaselineFile("CreateProcedure_BeginEnd.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateProcedure_Minimal() + { + LoadAndFormatAndCompare("CreateProcedure_Minimal", GetInputFile("CreateProcedure_Minimal.sql"), + GetBaselineFile("CreateProcedure_Minimal.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateProcedure_MultipleBatches() + { + LoadAndFormatAndCompare("CreateProcedure_MultipleBatches", GetInputFile("CreateProcedure_MultipleBatches.sql"), + GetBaselineFile("CreateProcedure_MultipleBatches.sql"), new FormatOptions(), true); + } + [Fact] + public void CreateProcedure_MultipleParams() + { + LoadAndFormatAndCompare("CreateProcedure_MultipleParams", GetInputFile("CreateProcedure_MultipleParams.sql"), + GetBaselineFile("CreateProcedure_MultipleParams.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateProcedure_OneParam() + { + LoadAndFormatAndCompare("CreateProcedure_OneParam", GetInputFile("CreateProcedure_OneParam.sql"), + GetBaselineFile("CreateProcedure_OneParam.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateProcedure_ParamsRecompileReturn() + { + LoadAndFormatAndCompare("CreateProcedure_ParamsRecompileReturn", GetInputFile("CreateProcedure_ParamsRecompileReturn.sql"), + GetBaselineFile("CreateProcedure_ParamsRecompileReturn.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateProcedure_Select() + { + LoadAndFormatAndCompare("CreateProcedure_Select", GetInputFile("CreateProcedure_Select.sql"), + GetBaselineFile("CreateProcedure_Select.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateProcedure_TwoPartName() + { + LoadAndFormatAndCompare("CreateProcedure_TwoPartName", GetInputFile("CreateProcedure_TwoPartName.sql"), + GetBaselineFile("CreateProcedure_TwoPartName.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateProcedure_WithCTE() + { + LoadAndFormatAndCompare("CreateProcedure_WithCTE", GetInputFile("CreateProcedure_WithCTE.sql"), + GetBaselineFile("CreateProcedure_WithCTE.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateProcedure_WithEncryptionModule() + { + LoadAndFormatAndCompare("CreateProcedure_WithEncryptionModule", GetInputFile("CreateProcedure_WithEncryptionModule.sql"), + GetBaselineFile("CreateProcedure_WithEncryptionModule.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateProcedure_WithExecuteAsModule() + { + LoadAndFormatAndCompare("CreateProcedure_WithExecuteAsModule", GetInputFile("CreateProcedure_WithExecuteAsModule.sql"), + GetBaselineFile("CreateProcedure_WithExecuteAsModule.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateProcedure_WithThreeModules() + { + LoadAndFormatAndCompare("CreateProcedure_WithThreeModules", GetInputFile("CreateProcedure_WithThreeModules.sql"), + GetBaselineFile("CreateProcedure_WithThreeModules.sql"), new FormatOptions(), true); + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/CreateTableFormatterTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/CreateTableFormatterTests.cs new file mode 100644 index 00000000..479ae462 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/CreateTableFormatterTests.cs @@ -0,0 +1,146 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.ServiceLayer.Formatter; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.Test.Formatter +{ + + public class CreateTableFormatterTests : FormatterUnitTestsBase + { + [Fact] + public void CreateTable() + { + LoadAndFormatAndCompare("CreateTable", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable.sql"), new FormatOptions(), true); + } + + /** + * The test contains a timestamp column, which is the shortest (1 token) possible length for a column item. + */ + [Fact] + public void CreateTable_Timestamp() + { + FormatOptions options = new FormatOptions(); + options.AlignColumnDefinitionsInColumns = true; + LoadAndFormatAndCompare("CreateTable_Timestamp", GetInputFile("CreateTable_Timestamp.sql"), GetBaselineFile("CreateTable_Timestamp.sql"), options, true); + } + + [Fact] + public void CreateTable_CommasBeforeDefinition() + { + FormatOptions options = new FormatOptions(); + options.PlaceCommasBeforeNextStatement = true; + + // TODO: fix verify to account for commma placement - this can + LoadAndFormatAndCompare("CreateTable_CommasBeforeDefinition", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable_CommasBeforeDefinition.sql"), options, false); + } + + [Fact] + public void CreateTable_UseTabs() + { + FormatOptions options = new FormatOptions(); + options.UseTabs = true; + LoadAndFormatAndCompare("CreateTable_UseTabs", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable_UseTabs.sql"), options, true); + } + + [Fact] + public void CreateTable_20Spaces() + { + FormatOptions options = new FormatOptions(); + options.SpacesPerIndent = 20; + LoadAndFormatAndCompare("CreateTable_20Spaces", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable_20Spaces.sql"), options, true); + } + + [Fact] + public void CreateTable_UpperCaseKeywords() + { + FormatOptions options = new FormatOptions(); + options.KeywordCasing = CasingOptions.Uppercase; + LoadAndFormatAndCompare("CreateTable_UpperCaseKeywords", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable_UpperCaseKeywords.sql"), options, true); + } + + [Fact] + public void CreateTable_LowerCaseKeywords() + { + FormatOptions options = new FormatOptions(); + options.KeywordCasing = CasingOptions.Lowercase; + LoadAndFormatAndCompare("CreateTable_LowerCaseKeywords", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable_LowerCaseKeywords.sql"), options, true); + } + + [Fact] + public void CreateTable_UpperCaseDataTypes() + { + FormatOptions options = new FormatOptions(); + options.DatatypeCasing = CasingOptions.Uppercase; + LoadAndFormatAndCompare("CreateTable_UpperCaseDataTypes", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable_UpperCaseDataTypes.sql"), options, true); + } + + [Fact] + public void CreateTable_LowerCaseDataTypes() + { + FormatOptions options = new FormatOptions(); + options.DatatypeCasing = CasingOptions.Lowercase; + LoadAndFormatAndCompare("CreateTable_LowerCaseDataTypes", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable_LowerCaseDataTypes.sql"), options, true); + } + + [Fact] + public void CreateTable_AlignInColumns() + { + FormatOptions options = new FormatOptions() { AlignColumnDefinitionsInColumns = true }; + LoadAndFormatAndCompare("CreateTable_AlignInColumns", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable_AlignInColumns.sql"), options, true); + } + + [Fact] + public void CreateTable_AlignInColumnsUseTabs() + { + FormatOptions options = new FormatOptions(); + options.UseTabs = true; + options.AlignColumnDefinitionsInColumns = true; + LoadAndFormatAndCompare("CreateTable_AlignInColumnsUseTabs", GetInputFile("CreateTable.sql"), GetBaselineFile("CreateTable_AlignInColumnsUseTabs.sql"), options, true); + } + + [Fact] + public void CreateTable_On() + { + LoadAndFormatAndCompare("CreateTableOn", GetInputFile("CreateTableFull.sql"), GetBaselineFile("CreateTableOn.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateTable_Formatted() + { + LoadAndFormatAndCompare("CreateTable_Formatted", GetInputFile("CreateTable_Formatted.sql"), GetBaselineFile("CreateTable_Formatted.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateTable_CommentsBeforeComma() + { + FormatOptions options = new FormatOptions(); + options.UseTabs = false; + options.AlignColumnDefinitionsInColumns = true; + options.PlaceCommasBeforeNextStatement = true; + LoadAndFormatAndCompare("CreateTable_CommentsBeforeComma", GetInputFile("CreateTable_CommentBeforeComma.sql"), GetBaselineFile("CreateTable_CommentBeforeComma.sql"), options, true); + } + + [Fact] + public void CreateTableAddress_AlignInColumns() + { + FormatOptions options = new FormatOptions(); + options.AlignColumnDefinitionsInColumns = true; + LoadAndFormatAndCompare("CreateTableAddress_AlignInColumns", GetInputFile("Address.sql"), GetBaselineFile("CreateTableAddress_AlignInColumns.sql"), options, true); + } + + [Fact] + public void CreateTableAddress_AlignInColumnsUseTabs() + { + FormatOptions options = new FormatOptions(); + options.UseTabs = true; + options.AlignColumnDefinitionsInColumns = true; + LoadAndFormatAndCompare("CreateTableAddress_AlignInColumnsUseTabs", GetInputFile("Address.sql"), GetBaselineFile("CreateTableAddress_AlignInColumnsUseTabs.sql"), options, true); + } + + + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/CreateViewFormatterTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/CreateViewFormatterTests.cs new file mode 100644 index 00000000..c0521652 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/CreateViewFormatterTests.cs @@ -0,0 +1,70 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + + +using Microsoft.SqlTools.ServiceLayer.Formatter; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.Test.Formatter +{ + + public class CreateViewFormatterTests : FormatterUnitTestsBase + { + [Fact] + public void CreateView_Full() + { + LoadAndFormatAndCompare("CreateView_Full", GetInputFile("CreateView_Full.sql"), + GetBaselineFile("CreateView_Full.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateView_FullWithComments() + { + LoadAndFormatAndCompare("CreateView_FullWithComments", GetInputFile("CreateView_FullWithComments.sql"), GetBaselineFile("CreateView_FullWithComments.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateView_MultipleColumns() + { + LoadAndFormatAndCompare("CreateView_MultipleColumns", GetInputFile("CreateView_MultipleColumns.sql"), + GetBaselineFile("CreateView_MultipleColumns.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateView_MultipleOptions() + { + LoadAndFormatAndCompare("CreateView_MultipleOptions", GetInputFile("CreateView_MultipleOptions.sql"), + GetBaselineFile("CreateView_MultipleOptions.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateView_OneColumn() + { + LoadAndFormatAndCompare("CreateView_OneColumn", GetInputFile("CreateView_OneColumn.sql"), + GetBaselineFile("CreateView_OneColumn.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateView_OneColumnOneOption() + { + LoadAndFormatAndCompare("CreateView_OneColumnOneOption", GetInputFile("CreateView_OneColumnOneOption.sql"), + GetBaselineFile("CreateView_OneColumnOneOption.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateView_OneOption() + { + LoadAndFormatAndCompare("CreateView_OneOption", GetInputFile("CreateView_OneOption.sql"), + GetBaselineFile("CreateView_OneOption.sql"), new FormatOptions(), true); + } + + [Fact] + public void CreateView_Simple() + { + LoadAndFormatAndCompare("CreateView_Simple", GetInputFile("CreateView_Simple.sql"), + GetBaselineFile("CreateView_Simple.sql"), new FormatOptions(), true); + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/FormatterSettingsTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/FormatterSettingsTests.cs new file mode 100644 index 00000000..3ce0f58e --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/FormatterSettingsTests.cs @@ -0,0 +1,108 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.ServiceLayer.Formatter; +using Microsoft.SqlTools.ServiceLayer.SqlContext; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.Test.Formatter +{ + public class FormatterSettingsTests + { + [Fact] + public void ValidateFormatterServiceDefaults() + { + var sqlToolsSettings = new SqlToolsSettings(); + Assert.Null(sqlToolsSettings.SqlTools.Format.AlignColumnDefinitionsInColumns); + Assert.Equal(CasingOptions.None, sqlToolsSettings.SqlTools.Format.DatatypeCasing); + Assert.Equal(CasingOptions.None, sqlToolsSettings.SqlTools.Format.KeywordCasing); + Assert.Null(sqlToolsSettings.SqlTools.Format.PlaceCommasBeforeNextStatement); + Assert.Null(sqlToolsSettings.SqlTools.Format.PlaceSelectStatementReferencesOnNewLine); + Assert.Null(sqlToolsSettings.SqlTools.Format.UseBracketForIdentifiers); + } + + [Fact] + public void ValidateFormatSettingsParsedFromJson() + { + const string settingsJson = @" +{ + ""params"": { + ""mssql"": { + ""format"": { + useBracketForIdentifiers: true, + placeCommasBeforeNextStatement: true, + placeSelectStatementReferencesOnNewLine: true, + keywordCasing: ""uppercase"", + datatypeCasing: ""lowercase"", + alignColumnDefinitionsInColumns: true + } + } + } +}"; + + JObject message = JObject.Parse(settingsJson); + JToken messageParams = null; + message.TryGetValue("params", out messageParams); + SqlToolsSettings sqlToolsSettings = messageParams.ToObject(); + + Assert.True(sqlToolsSettings.SqlTools.Format.AlignColumnDefinitionsInColumns); + Assert.Equal(CasingOptions.Lowercase, sqlToolsSettings.SqlTools.Format.DatatypeCasing); + Assert.Equal(CasingOptions.Uppercase, sqlToolsSettings.SqlTools.Format.KeywordCasing); + Assert.True(sqlToolsSettings.SqlTools.Format.PlaceCommasBeforeNextStatement); + Assert.True(sqlToolsSettings.SqlTools.Format.PlaceSelectStatementReferencesOnNewLine); + Assert.True(sqlToolsSettings.SqlTools.Format.UseBracketForIdentifiers); + } + + [Fact] + public void FormatOptionsMatchDefaultSettings() + { + var options = new FormatOptions(); + AssertOptionsHaveDefaultValues(options); + } + + private static void AssertOptionsHaveDefaultValues(FormatOptions options) + { + Assert.False(options.AlignColumnDefinitionsInColumns); + Assert.Equal(CasingOptions.None, options.DatatypeCasing); + Assert.Equal(CasingOptions.None, options.KeywordCasing); + Assert.False(options.PlaceCommasBeforeNextStatement); + Assert.False(options.PlaceEachReferenceOnNewLineInQueryStatements); + Assert.False(options.EncloseIdentifiersInSquareBrackets); + } + + [Fact] + public void CanCopyDefaultFormatSettingsToOptions() + { + var sqlToolsSettings = new SqlToolsSettings(); + FormatOptions options = new FormatOptions(); + TSqlFormatterService.UpdateFormatOptionsFromSettings(options, sqlToolsSettings.SqlTools.Format); + AssertOptionsHaveDefaultValues(options); + } + + [Fact] + public void CanCopyAlteredFormatSettingsToOptions() + { + var sqlToolsSettings = new SqlToolsSettings(); + sqlToolsSettings.SqlTools.Format.AlignColumnDefinitionsInColumns = true; + sqlToolsSettings.SqlTools.Format.DatatypeCasing = CasingOptions.Lowercase; + sqlToolsSettings.SqlTools.Format.KeywordCasing = CasingOptions.Uppercase; + sqlToolsSettings.SqlTools.Format.PlaceCommasBeforeNextStatement = true; + sqlToolsSettings.SqlTools.Format.PlaceSelectStatementReferencesOnNewLine = true; + sqlToolsSettings.SqlTools.Format.UseBracketForIdentifiers = true; + + FormatOptions options = new FormatOptions(); + TSqlFormatterService.UpdateFormatOptionsFromSettings(options, sqlToolsSettings.SqlTools.Format); + + Assert.True(options.AlignColumnDefinitionsInColumns); + Assert.Equal(CasingOptions.Lowercase, options.DatatypeCasing); + Assert.Equal(CasingOptions.Uppercase, options.KeywordCasing); + Assert.True(options.PlaceCommasBeforeNextStatement); + Assert.True(options.PlaceEachReferenceOnNewLineInQueryStatements); + Assert.True(options.EncloseIdentifiersInSquareBrackets); + } + + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/FormatterUnitTestBase.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/FormatterUnitTestBase.cs new file mode 100644 index 00000000..96c4cc77 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/FormatterUnitTestBase.cs @@ -0,0 +1,90 @@ +// +// 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.Reflection; +using Microsoft.SqlTools.ServiceLayer.Extensibility; +using Microsoft.SqlTools.ServiceLayer.Formatter; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.SqlContext; +using Microsoft.SqlTools.ServiceLayer.Test.Common; +using Microsoft.SqlTools.ServiceLayer.Workspace; +using Moq; + +namespace Microsoft.SqlTools.ServiceLayer.Test.Formatter +{ + public class FormatterUnitTestsBase + { + public FormatterUnitTestsBase() + { + HostMock = new Mock(); + WorkspaceServiceMock = new Mock>(); + ServiceProvider = ExtensionServiceProvider.CreateDefaultServiceProvider(); + ServiceProvider.RegisterSingleService(WorkspaceServiceMock.Object); + HostLoader.InitializeHostedServices(ServiceProvider, HostMock.Object); + FormatterService = ServiceProvider.GetService(); + } + + protected ExtensionServiceProvider ServiceProvider { get; private set; } + protected Mock HostMock { get; private set; } + protected Mock> WorkspaceServiceMock { get; private set; } + + protected TSqlFormatterService FormatterService { get; private set; } + + protected void LoadAndFormatAndCompare(string testName, FileInfo inputFile, FileInfo baselineFile, FormatOptions options, bool verifyFormat) + { + string inputSql = TestUtilities.ReadTextAndNormalizeLineEndings(inputFile.FullName); + string formattedSql = string.Empty; + formattedSql = FormatterService.Format(inputSql, options, verifyFormat); + + formattedSql = TestUtilities.NormalizeLineEndings(formattedSql); + + string assemblyPath = GetType().GetTypeInfo().Assembly.Location; + string directory = Path.Combine(Path.GetDirectoryName(assemblyPath), "FormatterTests"); + Directory.CreateDirectory(directory); + + FileInfo outputFile = new FileInfo(Path.Combine(directory, testName + ".out")); + File.WriteAllText(outputFile.FullName, formattedSql); + TestUtilities.CompareTestFiles(baselineFile, outputFile); + } + + public FileInfo GetInputFile(string fileName) + { + return new FileInfo(Path.Combine(InputFileDirectory.FullName, fileName)); + } + + public FileInfo GetBaselineFile(string fileName) + { + return new FileInfo(Path.Combine(BaselineDirectory.FullName, fileName)); + } + + public DirectoryInfo BaselineDirectory + { + get + { + string d = Path.Combine(TestLocationDirectory, "BaselineFiles"); + return new DirectoryInfo(d); + } + } + + public DirectoryInfo InputFileDirectory + { + get + { + string d = Path.Combine(TestLocationDirectory, "TestFiles"); + return new DirectoryInfo(d); + } + } + + private static string TestLocationDirectory + { + get + { + return Path.Combine(RunEnvironmentInfo.GetTestDataLocation(), "TSqlFormatter"); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/InsertFormatterTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/InsertFormatterTests.cs new file mode 100644 index 00000000..4e5d3cbb --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/InsertFormatterTests.cs @@ -0,0 +1,82 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + + +using Microsoft.SqlTools.ServiceLayer.Formatter; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.Test.Formatter +{ + + public class InsertFormatterTests : FormatterUnitTestsBase + { + [Fact] + public void Insert_DefaultValues() + { + LoadAndFormatAndCompare("Insert_DefaultValues", GetInputFile("Insert_DefaultValues.sql"), + GetBaselineFile("Insert_DefaultValues.sql"), new FormatOptions(), true); + } + + [Fact] + public void Insert_OpenQuery() + { + LoadAndFormatAndCompare("Insert_OpenQuery", GetInputFile("Insert_OpenQuery.sql"), + GetBaselineFile("Insert_OpenQuery.sql"), new FormatOptions(), true); + } + + [Fact] + public void Insert_OutputInto() + { + LoadAndFormatAndCompare("Insert_OutputInto", GetInputFile("Insert_OutputInto.sql"), + GetBaselineFile("Insert_OutputInto.sql"), new FormatOptions(), true); + } + + [Fact] + public void Insert_OutputStatement() + { + LoadAndFormatAndCompare("Insert_OutputStatement", GetInputFile("Insert_OutputStatement.sql"), + GetBaselineFile("Insert_OutputStatement.sql"), new FormatOptions(), true); + } + + [Fact] + public void Insert_Select() + { + FormatOptions options = new FormatOptions(); + options.PlaceEachReferenceOnNewLineInQueryStatements = true; + LoadAndFormatAndCompare("Insert_Select", GetInputFile("Insert_Select.sql"), + GetBaselineFile("Insert_Select.sql"), options, true); + } + + [Fact] + public void Insert_SelectSource() + { + FormatOptions options = new FormatOptions(); + options.PlaceEachReferenceOnNewLineInQueryStatements = true; + LoadAndFormatAndCompare("Insert_SelectSource", GetInputFile("Insert_SelectSource.sql"), + GetBaselineFile("Insert_SelectSource.sql"), options, true); + } + + [Fact] + public void Insert_TopSpecification() + { + LoadAndFormatAndCompare("Insert_TopSpecification", GetInputFile("Insert_TopSpecification.sql"), + GetBaselineFile("Insert_TopSpecification.sql"), new FormatOptions(), true); + } + + [Fact] + public void Insert_TopWithComments() + { + LoadAndFormatAndCompare("Insert_TopWithComments", GetInputFile("Insert_TopWithComments.sql"), + GetBaselineFile("Insert_TopWithComments.sql"), new FormatOptions(), true); + } + + [Fact] + public void Insert_Full() + { + LoadAndFormatAndCompare("Insert_Full", GetInputFile("Insert_Full.sql"), + GetBaselineFile("Insert_Full.sql"), new FormatOptions(), true); + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/SqlSelectStatementFormatterTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/SqlSelectStatementFormatterTests.cs new file mode 100644 index 00000000..30844a6f --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/SqlSelectStatementFormatterTests.cs @@ -0,0 +1,108 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.ServiceLayer.Formatter; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.Test.Formatter +{ + + public class SqlSelectStatementFormatterTests : FormatterUnitTestsBase + { + [Fact] + public void SimpleQuery() + { + LoadAndFormatAndCompare("SimpleQuery", GetInputFile("SimpleQuery.sql"), + GetBaselineFile("SimpleQuery.sql"), new FormatOptions(), true); + } + + [Fact] + public void SimpleQuery_CommasBeforeDefinition() + { + FormatOptions options = new FormatOptions(); + options.PlaceCommasBeforeNextStatement = true; + + // TODO: fix verify to account for commma placement - this can + LoadAndFormatAndCompare("SimpleQuery_CommasBeforeDefinition", GetInputFile("SimpleQuery.sql"), + GetBaselineFile("SimpleQuery_CommasBeforeDefinition.sql"), options, false); + } + + [Fact] + public void SimpleQuery_EachReferenceOnNewLine() + { + FormatOptions options = new FormatOptions(); + options.PlaceEachReferenceOnNewLineInQueryStatements = true; + + LoadAndFormatAndCompare("SimpleQuery_EachReferenceOnNewLine", GetInputFile("SimpleQuery.sql"), + GetBaselineFile("SimpleQuery_EachReferenceOnNewLine.sql"), options, true); + } + + [Fact] + public void SimpleQuery_EachReferenceOnNewLine_CommasBeforeDefinition() + { + FormatOptions options = new FormatOptions(); + options.PlaceCommasBeforeNextStatement = true; + options.PlaceEachReferenceOnNewLineInQueryStatements = true; + + // TODO: fix verify to account for commma placement - this can + LoadAndFormatAndCompare("SimpleQuery_EachReferenceOnNewLine_CommasBeforeDefinition", + GetInputFile("SimpleQuery.sql"), GetBaselineFile("SimpleQuery_EachReferenceOnNewLine_CommasBeforeDefinition.sql"), options, false); + } + + [Fact] + public void SimpleQuery_UseTabs() + { + FormatOptions options = new FormatOptions(); + options.UseTabs = true; + options.PlaceEachReferenceOnNewLineInQueryStatements = true; + LoadAndFormatAndCompare("SimpleQuery_UseTabs", GetInputFile("SimpleQuery.sql"), + GetBaselineFile("SimpleQuery_UseTabs.sql"), options, true); + } + + [Fact] + public void SimpleQuery_20Spaces() + { + FormatOptions options = new FormatOptions(); + options.SpacesPerIndent = 20; + options.PlaceEachReferenceOnNewLineInQueryStatements = true; + LoadAndFormatAndCompare("SimpleQuery_20Spaces", GetInputFile("SimpleQuery.sql"), + GetBaselineFile("SimpleQuery_20Spaces.sql"), options, true); + } + + [Fact] + public void SimpleQuery_UpperCaseKeywords() + { + FormatOptions options = new FormatOptions(); + options.KeywordCasing = CasingOptions.Uppercase; + options.PlaceEachReferenceOnNewLineInQueryStatements = true; + LoadAndFormatAndCompare("SimpleQuery_UpperCaseKeywords", GetInputFile("SimpleQuery.sql"), + GetBaselineFile("SimpleQuery_UpperCaseKeywords.sql"), options, true); + } + + [Fact] + public void SimpleQuery_LowerCaseKeywords() + { + FormatOptions options = new FormatOptions(); + options.KeywordCasing = CasingOptions.Lowercase; + options.PlaceEachReferenceOnNewLineInQueryStatements = true; + LoadAndFormatAndCompare("SimpleQuery_LowerCaseKeywords", GetInputFile("SimpleQuery.sql"), + GetBaselineFile("SimpleQuery_LowerCaseKeywords.sql"), options, true); + } + + [Fact] + public void SimpleQuery_ForBrowseClause() + { + LoadAndFormatAndCompare("SimpleQuery_ForBrowseClause", GetInputFile("SimpleQuery_ForBrowseClause.sql"), + GetBaselineFile("SimpleQuery_ForBrowseClause.sql"), new FormatOptions(), true); + } + + [Fact] + public void SimpleQuery_ForXmlClause() + { + LoadAndFormatAndCompare("SimpleQuery_ForXmlClause", GetInputFile("SimpleQuery_ForXmlClause.sql"), + GetBaselineFile("SimpleQuery_ForXmlClause.sql"), new FormatOptions(), true); + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/TSqlFormatterServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/TSqlFormatterServiceTests.cs new file mode 100644 index 00000000..6f7f4883 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Formatter/TSqlFormatterServiceTests.cs @@ -0,0 +1,95 @@ +// +// 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.Text; +using System.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.Formatter.Contracts; +using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; +using Microsoft.SqlTools.ServiceLayer.Test.Common; +using Microsoft.SqlTools.ServiceLayer.Test.Utility; +using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; +using Moq; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.Test.Formatter +{ + public class TSqlFormatterServiceTests : FormatterUnitTestsBase + { + private Mock workspaceMock = new Mock(); + private TextDocumentIdentifier textDocument; + DocumentFormattingParams docFormatParams; + + public TSqlFormatterServiceTests() + { + textDocument = new TextDocumentIdentifier + { + Uri = "script file" + }; + docFormatParams = new DocumentFormattingParams() + { + TextDocument = textDocument, + Options = new FormattingOptions() { InsertSpaces = true, TabSize = 4 } + }; + } + + private string defaultSqlContents = TestUtilities.NormalizeLineEndings(@"create TABLE T1 ( C1 int NOT NULL, C2 nvarchar(50) NULL)"); + // TODO fix bug where '\r' is appended + private string formattedSqlContents = TestUtilities.NormalizeLineEndings(@"create TABLE T1 +( + C1 int NOT NULL, + C2 nvarchar(50) NULL +)"); + + [Fact] + public async Task FormatDocumentShouldReturnSingleEdit() + { + // Given a document that we want to format + SetupScriptFile(defaultSqlContents); + // When format document is called + await TestUtils.RunAndVerify( + test: (requestContext) => FormatterService.HandleDocFormatRequest(docFormatParams, requestContext), + verify: (edits => + { + // Then expect a single edit to be returned and for it to match the standard formatting + Assert.Equal(1, edits.Length); + AssertFormattingEqual(formattedSqlContents, edits[0].NewText); + + })); + } + + private static void AssertFormattingEqual(string expected, string actual) + { + if (string.Compare(expected, actual) != 0) + { + StringBuilder error = new StringBuilder(); + error.AppendLine("======================"); + error.AppendLine("Comparison failed:"); + error.AppendLine("==Expected=="); + error.AppendLine(expected); + error.AppendLine("==Actual=="); + error.AppendLine(actual); + Assert.False(false, error.ToString()); + } + } + + private void SetupScriptFile(string fileContents) + { + WorkspaceServiceMock.SetupGet(service => service.Workspace).Returns(workspaceMock.Object); + workspaceMock.Setup(w => w.GetFile(It.IsAny())).Returns(CreateScriptFile(fileContents)); + } + + private ScriptFile CreateScriptFile(string content) + { + ScriptFile scriptFile = new ScriptFile() + { + Contents = content + }; + return scriptFile; + } + + + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs index d751b57c..0b7efbb7 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/PeekDefinitionTests.cs @@ -141,11 +141,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices [Fact] public void GetLocationFromFileForValidFilePathTest() { - String filePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "C:\\test\\script.sql" : "/test/script.sql"; + string filePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "C:\\test\\script.sql" : "/test/script.sql"; PeekDefinition peekDefinition = new PeekDefinition(null, null); Location[] locations = peekDefinition.GetLocationFromFile(filePath, 0); - String expectedFilePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "file:///C:/test/script.sql" : "file:/test/script.sql"; + string expectedFilePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "file:///C:/test/script.sql" : "file:/test/script.sql"; Assert.Equal(locations[0].Uri, expectedFilePath); } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/ServiceHost/ScriptFileTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/ServiceHost/ScriptFileTests.cs index 817f3328..c5023d25 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/ServiceHost/ScriptFileTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/ServiceHost/ScriptFileTests.cs @@ -119,7 +119,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost [Fact] public void CanApplySingleLineInsert() { - this.AssertFileChange( + AssertFileChange( "This is a test.", "This is a working test.", new FileChange @@ -135,7 +135,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost [Fact] public void CanApplySingleLineReplace() { - this.AssertFileChange( + AssertFileChange( "This is a potentially broken test.", "This is a working test.", new FileChange @@ -151,7 +151,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost [Fact] public void CanApplySingleLineDelete() { - this.AssertFileChange( + AssertFileChange( "This is a test of the emergency broadcasting system.", "This is a test.", new FileChange @@ -167,7 +167,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost [Fact] public void CanApplyMultiLineInsert() { - this.AssertFileChange( + AssertFileChange( "first\r\nsecond\r\nfifth", "first\r\nsecond\r\nthird\r\nfourth\r\nfifth", new FileChange @@ -183,7 +183,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost [Fact] public void CanApplyMultiLineReplace() { - this.AssertFileChange( + AssertFileChange( "first\r\nsecoXX\r\nXXfth", "first\r\nsecond\r\nthird\r\nfourth\r\nfifth", new FileChange @@ -199,7 +199,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost [Fact] public void CanApplyMultiLineReplaceWithRemovedLines() { - this.AssertFileChange( + AssertFileChange( "first\r\nsecoXX\r\nREMOVE\r\nTHESE\r\nLINES\r\nXXfth", "first\r\nsecond\r\nthird\r\nfourth\r\nfifth", new FileChange @@ -215,7 +215,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost [Fact] public void CanApplyMultiLineDelete() { - this.AssertFileChange( + AssertFileChange( "first\r\nsecond\r\nREMOVE\r\nTHESE\r\nLINES\r\nthird", "first\r\nsecond\r\nthird", new FileChange @@ -235,7 +235,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost typeof(ArgumentOutOfRangeException), () => { - this.AssertFileChange( + AssertFileChange( "first\r\nsecond\r\nREMOVE\r\nTHESE\r\nLINES\r\nthird", "first\r\nsecond\r\nthird", new FileChange @@ -275,7 +275,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost public ScriptFileGetLinesTests() { - this.scriptFile = + scriptFile = ScriptFileTests.GetTestScriptFile( "Line One\r\nLine Two\r\nLine Three\r\nLine Four\r\nLine Five\r\n"); } @@ -284,7 +284,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost public void CanGetWholeLine() { string[] lines = - this.scriptFile.GetLinesInRange( + scriptFile.GetLinesInRange( new BufferRange(5, 1, 5, 10)); Assert.Equal(1, lines.Length); @@ -295,7 +295,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost public void CanGetMultipleWholeLines() { string[] lines = - this.scriptFile.GetLinesInRange( + scriptFile.GetLinesInRange( new BufferRange(2, 1, 4, 10)); Assert.Equal(TestStringLines.Skip(1).Take(3), lines); @@ -305,7 +305,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost public void CanGetSubstringInSingleLine() { string[] lines = - this.scriptFile.GetLinesInRange( + scriptFile.GetLinesInRange( new BufferRange(4, 3, 4, 8)); Assert.Equal(1, lines.Length); @@ -316,7 +316,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost public void CanGetEmptySubstringRange() { string[] lines = - this.scriptFile.GetLinesInRange( + scriptFile.GetLinesInRange( new BufferRange(4, 3, 4, 3)); Assert.Equal(1, lines.Length); @@ -334,7 +334,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost }; string[] lines = - this.scriptFile.GetLinesInRange( + scriptFile.GetLinesInRange( new BufferRange(2, 6, 4, 9)); Assert.Equal(expectedLines, lines); @@ -351,7 +351,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost }; string[] lines = - this.scriptFile.GetLinesInRange( + scriptFile.GetLinesInRange( new BufferRange(2, 9, 4, 1)); Assert.Equal(expectedLines, lines); @@ -364,7 +364,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost public ScriptFilePositionTests() { - this.scriptFile = + scriptFile = ScriptFileTests.GetTestScriptFile(@" First line Second line is longer @@ -375,12 +375,12 @@ First line [Fact] public void CanOffsetByLine() { - this.AssertNewPosition( + AssertNewPosition( 1, 1, 2, 0, 3, 1); - this.AssertNewPosition( + AssertNewPosition( 3, 1, -2, 0, 1, 1); @@ -389,12 +389,12 @@ First line [Fact] public void CanOffsetByColumn() { - this.AssertNewPosition( + AssertNewPosition( 2, 1, 0, 2, 2, 3); - this.AssertNewPosition( + AssertNewPosition( 2, 5, 0, -3, 2, 2); @@ -447,7 +447,7 @@ First line [Fact] public void CanFindBeginningOfLine() { - this.AssertNewPosition( + AssertNewPosition( 4, 12, pos => pos.GetLineStart(), 4, 5); @@ -456,7 +456,7 @@ First line [Fact] public void CanFindEndOfLine() { - this.AssertNewPosition( + AssertNewPosition( 4, 12, pos => pos.GetLineEnd(), 4, 15); @@ -465,7 +465,7 @@ First line [Fact] public void CanComposePositionOperations() { - this.AssertNewPosition( + AssertNewPosition( 4, 12, pos => pos.AddOffset(-1, 1).GetLineStart(), 3, 3); @@ -476,7 +476,7 @@ First line int lineOffset, int columnOffset, int expectedLine, int expectedColumn) { - this.AssertNewPosition( + AssertNewPosition( originalLine, originalColumn, pos => pos.AddOffset(lineOffset, columnOffset), expectedLine, expectedColumn); @@ -490,7 +490,7 @@ First line var newPosition = positionOperation( new FilePosition( - this.scriptFile, + scriptFile, originalLine, originalColumn)); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/SqlContext/SettingsTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/SqlContext/SettingsTests.cs index e06764de..c0677307 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/SqlContext/SettingsTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/SqlContext/SettingsTests.cs @@ -75,7 +75,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices Assert.False(sqlToolsSettings.IsSuggestionsEnabled); } - /// + /// /// Validate that the IsQuickInfoEnabled flag behavior /// [Fact] diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestUtils.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestUtils.cs index a1a88c13..10db8c87 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestUtils.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestUtils.cs @@ -2,6 +2,9 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; +using Moq; +using Xunit; namespace Microsoft.SqlTools.ServiceLayer.Test.Utility { @@ -49,5 +52,35 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Utility return (count < intervalCount); } + + + public static async Task RunAndVerify(Func, Task> test, Action verify) + { + T result = default(T); + var contextMock = RequestContextMocks.Create(r => result = r).AddErrorHandling(null); + await test(contextMock.Object); + VerifyResult(contextMock, verify, result); + } + + public static void VerifyErrorSent(Mock> contextMock) + { + contextMock.Verify(c => c.SendResult(It.IsAny()), Times.Never); + contextMock.Verify(c => c.SendError(It.IsAny()), Times.Once); + } + + public static void VerifyResult(Mock> contextMock, U expected, U actual) + { + contextMock.Verify(c => c.SendResult(It.IsAny()), Times.Once); + Assert.Equal(expected, actual); + contextMock.Verify(c => c.SendError(It.IsAny()), Times.Never); + } + + public static void VerifyResult(Mock> contextMock, Action verify, T actual) + { + contextMock.Verify(c => c.SendResult(It.IsAny()), Times.Once); + contextMock.Verify(c => c.SendError(It.IsAny()), Times.Never); + verify(actual); + } + } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Driver/ServiceTestDriver.cs b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Driver/ServiceTestDriver.cs index 637c064c..01d50f08 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Driver/ServiceTestDriver.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.TestDriver/Driver/ServiceTestDriver.cs @@ -18,7 +18,6 @@ using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel; using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; -using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests; namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Driver @@ -101,6 +100,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TestDriver.Driver startTime = DateTime.Now; // Launch the process + this.protocolClient.Initialize(); await this.protocolClient.Start(); await Task.Delay(1000); // Wait for the service host to start diff --git a/test/Microsoft.SqlTools.ServiceLayer.TestEnvConfig/Program.cs b/test/Microsoft.SqlTools.ServiceLayer.TestEnvConfig/Program.cs index 3c801203..a22852a8 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.TestEnvConfig/Program.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.TestEnvConfig/Program.cs @@ -26,7 +26,7 @@ namespace Microsoft.SqlTools.ServiceLayer.TestEnvConfig { ShowUsage(); } - else if (File.Exists(arg) == false) + else if (!File.Exists(arg)) { Console.WriteLine("setting file {0} does not exist.", arg); }