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/bin/nuget/mssql.ResX.nupkg b/bin/nuget/mssql.ResX.nupkg
new file mode 100644
index 00000000..0012be07
Binary files /dev/null and b/bin/nuget/mssql.ResX.nupkg differ
diff --git a/bin/nuget/mssql.XliffParser.nupkg b/bin/nuget/mssql.XliffParser.nupkg
new file mode 100644
index 00000000..dbae7fbf
Binary files /dev/null and b/bin/nuget/mssql.XliffParser.nupkg differ
diff --git a/build.cake b/build.cake
index 595c7818..9005628b 100644
--- a/build.cake
+++ b/build.cake
@@ -1,4 +1,6 @@
#addin "Newtonsoft.Json"
+#addin "mssql.ResX"
+#addin "mssql.XliffParser"
#load "scripts/runhelpers.cake"
#load "scripts/archiving.cake"
@@ -9,7 +11,8 @@ using System.ComponentModel;
using System.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-using Cake.Common.IO
+using Cake.Common.IO;
+using XliffParser;
// Basic arguments
var target = Argument("target", "Default");
@@ -510,39 +513,58 @@ Task("SetPackageVersions")
Task("SRGen")
.Does(() =>
{
- var projects = System.IO.Directory.GetFiles(sourceFolder, "project.json", SearchOption.AllDirectories).ToList();
- foreach(var project in projects) {
- var projectDir = System.IO.Path.GetDirectoryName(project);
- var projectName = (new System.IO.DirectoryInfo(projectDir)).Name;
- var projectStrings = System.IO.Path.Combine(projectDir, "sr.strings");
+ var projects = System.IO.Directory.GetFiles(sourceFolder, "project.json", SearchOption.AllDirectories).ToList();
- if (!System.IO.File.Exists(projectStrings))
- {
- Information("Project {0} doesn't contain 'sr.strings' file", projectName);
- continue;
- }
+ foreach(var project in projects) {
+ var projectDir = System.IO.Path.GetDirectoryName(project);
+ var localizationDir = System.IO.Path.Combine(projectDir, "Localization");
+ var projectName = (new System.IO.DirectoryInfo(projectDir)).Name;
+ var projectNameSpace = projectName + ".Localization";
+ var projectStrings = System.IO.Path.Combine(localizationDir, "sr.strings");
- var srgenPath = System.IO.Path.Combine(toolsFolder, "Microsoft.DataTools.SrGen", "lib", "netcoreapp1.0", "srgen.dll");
- var outputResx = System.IO.Path.Combine(projectDir, "sr.resx");
- var outputCs = System.IO.Path.Combine(projectDir, "sr.cs");
+ if (!System.IO.File.Exists(projectStrings))
+ {
+ Information("Project {0} doesn't contain 'sr.strings' file", projectName);
+ continue;
+ }
- // Delete preexisting resx and designer files
- if (System.IO.File.Exists(outputResx))
- {
- System.IO.File.Delete(outputResx);
- }
- if (System.IO.File.Exists(outputCs))
- {
- System.IO.File.Delete(outputCs);
- }
+ var srgenPath = System.IO.Path.Combine(toolsFolder, "Microsoft.DataTools.SrGen", "lib", "netcoreapp1.0", "srgen.dll");
+ var outputResx = System.IO.Path.Combine(localizationDir, "sr.resx");
+ var inputXliff = System.IO.Path.Combine(localizationDir, "transXliff");
+ var outputXlf = System.IO.Path.Combine(localizationDir, "sr.xlf");
+ var outputCs = System.IO.Path.Combine(localizationDir, "sr.cs");
- // Run SRGen
- var dotnetArgs = string.Format("{0} -or \"{1}\" -oc \"{2}\" -ns \"{3}\" -an \"{4}\" -cn SR -l CS -dnx \"{5}\"",
- srgenPath, outputResx, outputCs, projectName, projectName, projectStrings);
- Information("{0}", dotnetArgs);
- Run(dotnetcli, dotnetArgs)
- .ExceptionOnError("Failed to run SRGen.");
- }
+ // Delete preexisting resx and designer files
+ if (System.IO.File.Exists(outputResx))
+ {
+ System.IO.File.Delete(outputResx);
+ }
+ if (System.IO.File.Exists(outputCs))
+ {
+ System.IO.File.Delete(outputCs);
+ }
+
+ // Run SRGen
+ var dotnetArgs = string.Format("{0} -or \"{1}\" -oc \"{2}\" -ns \"{3}\" -an \"{4}\" -cn SR -l CS -dnx \"{5}\"",
+ srgenPath, outputResx, outputCs, projectName, projectNameSpace, projectStrings);
+ Information("{0}", dotnetArgs);
+ Run(dotnetcli, dotnetArgs)
+ .ExceptionOnError("Failed to run SRGen.");
+
+ // Update XLF file from new Resx file
+ var doc = new XliffParser.XlfDocument(outputXlf);
+ doc.UpdateFromSource();
+ doc.Save();
+
+ // Update ResX files from new xliff files
+ var xlfDocNames = System.IO.Directory.GetFiles(inputXliff, "*.xlf", SearchOption.AllDirectories).ToList();
+ foreach(var docName in xlfDocNames)
+ {
+ var xlfDoc = new XliffParser.XlfDocument(docName);
+ var newPath = System.IO.Path.Combine(localizationDir, System.IO.Path.GetFileName(docName));
+ xlfDoc.SaveAsResX(newPath.Replace("xlf","resx"));
+ }
+ }
});
///
diff --git a/scripts/cake-bootstrap.ps1 b/scripts/cake-bootstrap.ps1
index 7f0bc382..a6e34175 100644
--- a/scripts/cake-bootstrap.ps1
+++ b/scripts/cake-bootstrap.ps1
@@ -89,6 +89,7 @@ if(-Not $SkipToolPackageRestore.IsPresent)
Set-Location $TOOLS_DIR
Write-Verbose -Message "Restoring tools from NuGet..."
+ $NuGetConfig = Invoke-Expression "&`"$NUGET_EXE`" config -configfile ../nuget.config"
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install $PACKAGES_CONFIG -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`""
Write-Verbose -Message ($NuGetOutput | out-string)
diff --git a/scripts/cake-bootstrap.sh b/scripts/cake-bootstrap.sh
index abc3ed43..6d8f0664 100644
--- a/scripts/cake-bootstrap.sh
+++ b/scripts/cake-bootstrap.sh
@@ -48,6 +48,7 @@ fi
# Restore tools from NuGet.
pushd "$TOOLS_DIR" >/dev/null
+mono "$NUGET_EXE" config -configfile ../nuget.config
mono "$NUGET_EXE" install "$PACKAGES_CONFIG" -ExcludeVersion -OutputDirectory "$TOOLS_DIR"
if [ $? -ne 0 ]; then
echo "Could not restore NuGet packages."
diff --git a/scripts/packages.config b/scripts/packages.config
index fd0de851..10e4f480 100644
--- a/scripts/packages.config
+++ b/scripts/packages.config
@@ -4,4 +4,5 @@
+
\ 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..86050aed 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs
@@ -328,7 +328,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
}
catch (ObjectDisposedException)
{
- // Ignore
+ // If ObjectDisposedException was thrown, then execution has already exited the
+ // "using" statment and source was disposed, meaning that the openTask completed
+ // successfully. This results in a ObjectDisposedException when trying to access
+ // source.Token and should be ignored.
}
});
@@ -720,7 +723,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 +748,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..58ffdb08
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Formatter/TSqlFormatterService.cs
@@ -0,0 +1,267 @@
+//
+// 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)
+ {
+ return MergeFormatOptions(docFormatParams.Options, settings);
+ }
+
+ internal static FormatOptions MergeFormatOptions(FormattingOptions formatRequestOptions, FormatterSettings settings)
+
+ {
+ FormatOptions options = new FormatOptions();
+ if (formatRequestOptions != null)
+ {
+ options.UseSpaces = formatRequestOptions.InsertSpaces;
+ options.SpacesPerIndent = formatRequestOptions.TabSize;
+ }
+ UpdateFormatOptionsFromSettings(options, settings);
+ 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/BindingQueue.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs
index b7808509..bf78583e 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs
@@ -123,6 +123,28 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
}
+ ///
+ /// Remove the binding queue entry
+ ///
+ protected void RemoveBindingContext(string key)
+ {
+ lock (this.bindingContextLock)
+ {
+ if (this.BindingContextMap.ContainsKey(key))
+ {
+ // disconnect existing connection
+ var bindingContext = this.BindingContextMap[key];
+ if (bindingContext.ServerConnection != null && bindingContext.ServerConnection.IsOpen)
+ {
+ bindingContext.ServerConnection.Disconnect();
+ }
+
+ // remove key from the map
+ this.BindingContextMap.Remove(key);
+ }
+ }
+ }
+
private bool HasPendingQueueItems
{
get
diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs
index bd767a7a..a74810d5 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs
@@ -51,8 +51,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
///
/// Use a ConnectionInfo item to create a connected binding context
///
- ///
- public virtual string AddConnectionContext(ConnectionInfo connInfo)
+ /// Connection info used to create binding context
+ /// Overwrite existing context
+ public virtual string AddConnectionContext(ConnectionInfo connInfo, bool overwrite = false)
{
if (connInfo == null)
{
@@ -63,8 +64,15 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
string connectionKey = GetConnectionContextKey(connInfo);
if (BindingContextExists(connectionKey))
{
- // no need to populate the context again since the context already exists
- return connectionKey;
+ if (overwrite)
+ {
+ RemoveBindingContext(connectionKey);
+ }
+ else
+ {
+ // no need to populate the context again since the context already exists
+ return connectionKey;
+ }
}
IBindingContext bindingContext = this.GetOrCreateBindingContext(connectionKey);
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/RebuildIntelliSenseNotification.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/RebuildIntelliSenseNotification.cs
new file mode 100644
index 00000000..f5c52591
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/RebuildIntelliSenseNotification.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 Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
+
+namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
+{
+ ///
+ /// Parameters to be sent back with a rebuild IntelliSense event
+ ///
+ public class RebuildIntelliSenseParams
+ {
+ ///
+ /// URI identifying the file that should have its IntelliSense cache rebuilt
+ ///
+ public string OwnerUri { get; set; }
+ }
+
+ ///
+ /// RebuildIntelliSenseNotification notification mapping entry
+ ///
+ public class RebuildIntelliSenseNotification
+ {
+ public static readonly
+ EventType Type =
+ EventType.Create("textDocument/rebuildIntelliSense");
+ }
+}
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/LanguageServices/LanguageService.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs
index 46a59afb..2229f893 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs
@@ -212,6 +212,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
serviceHost.SetRequestHandler(HoverRequest.Type, HandleHoverRequest);
serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest);
serviceHost.SetRequestHandler(DefinitionRequest.Type, HandleDefinitionRequest);
+ serviceHost.SetEventHandler(RebuildIntelliSenseNotification.Type, HandleRebuildIntelliSenseNotification);
// Register a no-op shutdown task for validation of the shutdown logic
serviceHost.RegisterShutdownTask(async (shutdownParams, shutdownRequestContext) =>
@@ -442,6 +443,62 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
await Task.FromResult(true);
}
+ ///
+ /// Handle the rebuild IntelliSense cache notification
+ ///
+ public async Task HandleRebuildIntelliSenseNotification(
+ RebuildIntelliSenseParams configChangeParams,
+ EventContext eventContext)
+ {
+ Logger.Write(LogLevel.Verbose, "HandleRebuildIntelliSenseNotification");
+
+ // Skip closing this file if the file doesn't exist
+ var scriptFile = this.CurrentWorkspace.GetFile(configChangeParams.OwnerUri);
+ if (scriptFile == null)
+ {
+ return;
+ }
+
+ ConnectionInfo connInfo;
+ LanguageService.ConnectionServiceInstance.TryFindConnection(
+ scriptFile.ClientFilePath,
+ out connInfo);
+
+ await Task.Run(() =>
+ {
+ ScriptParseInfo scriptInfo = GetScriptParseInfo(connInfo.OwnerUri, createIfNotExists: false);
+ if (scriptInfo != null && scriptInfo.IsConnected &&
+ Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
+ {
+ try
+ {
+ this.BindingQueue.AddConnectionContext(connInfo, overwrite: true);
+ }
+ catch (Exception ex)
+ {
+ Logger.Write(LogLevel.Error, "Unknown error " + ex.ToString());
+ }
+ finally
+ {
+ // Set Metadata Build event to Signal state.
+ Monitor.Exit(scriptInfo.BuildingMetadataLock);
+ }
+ }
+
+ // if not in the preview window and diagnostics are enabled then run diagnostics
+ if (!IsPreviewWindow(scriptFile)
+ && WorkspaceService.Instance.CurrentSettings.IsDiagnositicsEnabled)
+ {
+ RunScriptDiagnostics(
+ new ScriptFile[] { scriptFile },
+ eventContext);
+ }
+
+ // Send a notification to signal that autocomplete is ready
+ ServiceHost.Instance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() {OwnerUri = connInfo.OwnerUri});
+ });
+ }
+
///
/// Handle the file configuration change notification
///
diff --git a/src/Microsoft.SqlTools.ServiceLayer/sr.Designer.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.Designer.cs
similarity index 92%
rename from src/Microsoft.SqlTools.ServiceLayer/sr.Designer.cs
rename to src/Microsoft.SqlTools.ServiceLayer/Localization/sr.Designer.cs
index dbf28fba..d7cf2923 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/sr.Designer.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.Designer.cs
@@ -8,7 +8,7 @@
//
//------------------------------------------------------------------------------
-namespace Microsoft.SqlTools.ServiceLayer {
+namespace Microsoft.SqlTools.ServiceLayer.Localization {
using System;
using System.Reflection;
@@ -38,7 +38,7 @@ namespace Microsoft.SqlTools.ServiceLayer {
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.SqlTools.ServiceLayer.sr", typeof(sr).GetTypeInfo().Assembly);
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.SqlTools.ServiceLayer.Localization.sr", typeof(sr).GetTypeInfo().Assembly);
resourceMan = temp;
}
return resourceMan;
@@ -446,6 +446,24 @@ namespace Microsoft.SqlTools.ServiceLayer {
}
}
+ ///
+ /// Looks up a localized string similar to Replacement of an empty string by an empty string..
+ ///
+ public static string ErrorEmptyStringReplacement {
+ get {
+ return ResourceManager.GetString("ErrorEmptyStringReplacement", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Cannot convert SqlCodeObject Type {0} to Type {1}.
+ ///
+ public static string ErrorUnexpectedCodeObjectType {
+ get {
+ return ResourceManager.GetString("ErrorUnexpectedCodeObjectType", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Message header must separate key and value using ':'.
///
@@ -482,6 +500,24 @@ namespace Microsoft.SqlTools.ServiceLayer {
}
}
+ ///
+ /// Looks up a localized string similar to Service of type {0} cannot be created by ExtensionLoader<{1}>.
+ ///
+ public static string IncompatibleServiceForExtensionLoader {
+ get {
+ return ResourceManager.GetString("IncompatibleServiceForExtensionLoader", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Multiple services found for type {0}, expected only 1.
+ ///
+ public static string MultipleServicesFound {
+ get {
+ return ResourceManager.GetString("MultipleServicesFound", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to This feature is currently not supported on Azure SQL DB and Data Warehouse: {0}.
///
@@ -833,6 +869,42 @@ namespace Microsoft.SqlTools.ServiceLayer {
}
}
+ ///
+ /// Looks up a localized string similar to Cannot register service for type {0}, one or more services already registered.
+ ///
+ public static string ServiceAlreadyRegistered {
+ get {
+ return ResourceManager.GetString("ServiceAlreadyRegistered", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Service {0} was not found in the service provider.
+ ///
+ public static string ServiceNotFound {
+ get {
+ return ResourceManager.GetString("ServiceNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Service of Type {0} is not compatible with registered Type {1}.
+ ///
+ public static string ServiceNotOfExpectedType {
+ get {
+ return ResourceManager.GetString("ServiceNotOfExpectedType", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to SetServiceProvider() was not called to establish the required service provider.
+ ///
+ public static string ServiceProviderNotSet {
+ get {
+ return ResourceManager.GetString("ServiceProviderNotSet", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to EN_LOCALIZATION.
///
diff --git a/src/Microsoft.SqlTools.ServiceLayer/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs
similarity index 92%
rename from src/Microsoft.SqlTools.ServiceLayer/sr.cs
rename to src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs
index 3bb109f3..221bfe95 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/sr.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/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
@@ -724,7 +788,7 @@ namespace Microsoft.SqlTools.ServiceLayer
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Keys
{
- static ResourceManager resourceManager = new ResourceManager("Microsoft.SqlTools.ServiceLayer.SR", typeof(SR).GetTypeInfo().Assembly);
+ static ResourceManager resourceManager = new ResourceManager("Microsoft.SqlTools.ServiceLayer.Localization.SR", typeof(SR).GetTypeInfo().Assembly);
static CultureInfo _culture = null;
@@ -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.es.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.es.resx
similarity index 99%
rename from src/Microsoft.SqlTools.ServiceLayer/sr.es.resx
rename to src/Microsoft.SqlTools.ServiceLayer/Localization/sr.es.resx
index b1657f70..d65a6f96 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/sr.es.resx
+++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.es.resx
@@ -1,4 +1,4 @@
-
+