TSQL Formatter Service (#229)

- TSqlFormatterService with support for formatting document and text range inside document
- Settings support for all formatting options.
- Extensibility support so that the service can be initialized using MEF extensibility, and can find all necessary TSqlFormatters using the same process

Fix Initialize request error on startup
- Messages were being read from the input channel before all request handlers were registered
- In particular, the Initialize request which is key for any server to talk to the client was getting lost because the message reader thread begins consuming, and we take an extra few hundred milliseconds due to MEF startup before we register the handler
- The solution is to initialize the message handler so request handlers can register, but not actually start processing incoming messages until all handers are ready. This is a safer way to go and should improve reliability overall

Improvements from internal prototype:
- Normalizing baselines to handle the line ending differences on Mac & Linux vs. Windows
- Significantly shortened most lines by implementing base class methods to wrap common objects from Visitor.Context and removing unnecessary "this." syntax
- Refactored the SqlCommonTableExpressionFormatter and related classes to reduce code count significantly. This provides a pattern to follow when refactoring other classes for similar clarity. It's likely a lot of common logic could be found and reused across these.
- Reduced overall code size by adding utility methods
This commit is contained in:
Kevin Cunnane
2017-02-14 23:40:17 -08:00
committed by GitHub
parent eb4f2f2b91
commit 7477642854
212 changed files with 7905 additions and 184 deletions

View File

@@ -720,7 +720,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
}
// open connection based on request details
result = await Instance.Connect(connectParams);
result = await Connect(connectParams);
await ServiceHost.SendEvent(ConnectionCompleteNotification.Type, result);
}
catch (Exception ex)
@@ -745,7 +745,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection
try
{
bool result = Instance.CancelConnect(cancelParams);
bool result = CancelConnect(cancelParams);
await requestContext.SendResult(result);
}
catch(Exception ex)

View File

@@ -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

View File

@@ -58,7 +58,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Credentials
/// </summary>
internal CredentialService(ICredentialStore store, StoreConfig config)
{
this.credStore = store != null ? store : GetStoreForOS(config);
credStore = store != null ? store : GetStoreForOS(config);
}
/// <summary>

View File

@@ -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<ConventionBuilder, ContainerConfiguration> config;
public ExtensionServiceProvider(Func<ConventionBuilder, ContainerConfiguration> 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<Assembly> assemblies)
{
Validate.IsNotNull(nameof(assemblies), assemblies);
return new ExtensionServiceProvider(conventions => new ContainerConfiguration().WithAssemblies(assemblies, conventions));
}
public static ExtensionServiceProvider Create(IEnumerable<Type> types)
{
Validate.IsNotNull(nameof(types), types);
return new ExtensionServiceProvider(conventions => new ContainerConfiguration().WithParts(types, conventions));
}
protected override IEnumerable<T> GetServicesImpl<T>()
{
EnsureExtensionStoreRegistered<T>();
return base.GetServicesImpl<T>();
}
private void EnsureExtensionStoreRegistered<T>()
{
if (!services.ContainsKey(typeof(T)))
{
ExtensionStore store = new ExtensionStore(typeof(T), config);
base.Register<T>(() => store.GetExports<T>());
}
}
}
/// <summary>
/// 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.
/// </summary>
public class ExtensionStore
{
private CompositionHost host;
private IList exports;
private Type contractType;
/// <summary>
/// Initializes the store with a type to lookup exports of, and a function that configures the
/// lookup parameters.
/// </summary>
/// <param name="contractType">Type to use as a base for all extensions being looked up</param>
/// <param name="configure">Function that returns the configuration to be used</param>
public ExtensionStore(Type contractType, Func<ConventionBuilder, ContainerConfiguration> configure)
{
Validate.IsNotNull(nameof(contractType), contractType);
Validate.IsNotNull(nameof(configure), configure);
this.contractType = contractType;
ConventionBuilder builder = GetExportBuilder();
ContainerConfiguration config = configure(builder);
host = config.CreateContainer();
}
/// <summary>
/// Loads extensions from the current assembly
/// </summary>
/// <returns>ExtensionStore</returns>
public static ExtensionStore CreateDefaultLoader<T>()
{
return CreateAssemblyStore<T>(typeof(ExtensionStore).GetTypeInfo().Assembly);
}
public static ExtensionStore CreateAssemblyStore<T>(Assembly assembly)
{
Validate.IsNotNull(nameof(assembly), assembly);
return new ExtensionStore(typeof(T), (conventions) =>
new ContainerConfiguration().WithAssembly(assembly, conventions));
}
public static ExtensionStore CreateStoreForCurrentDirectory<T>()
{
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<T> GetExports<T>()
{
if (exports == null)
{
exports = host.GetExports(contractType).ToList();
}
return exports.Cast<T>();
}
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);
}
}
}

View File

@@ -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
{
/// <summary>
/// A Service that expects to lookup other services. Using this interface on an exported service
/// will ensure the <see cref="SetServiceProvider(IMultiServiceProvider)"/> method is called during
/// service initialization
/// </summary>
public interface IComposableService
{
/// <summary>
/// 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.
/// </summary>
void SetServiceProvider(IMultiServiceProvider provider);
}
}

View File

@@ -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<T> SingleItemAsEnumerable<T>(this T item)
{
yield return item;
}
}
}

View File

@@ -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
{
/// <summary>
/// Gets a service of a specific type. It is expected that only 1 instance of this type will be
/// available
/// </summary>
/// <typeparam name="T">Type of service to be found</typeparam>
/// <returns>Instance of T or null if not found</returns>
/// <exception cref="InvalidOperationException">The input sequence contains more than one element.-or-The input sequence is empty.</exception>
T GetService<T>();
/// <summary>
/// Gets a service of a specific type. The first service matching the specified filter will be returned
/// available
/// </summary>
/// <typeparam name="T">Type of service to be found</typeparam>
/// <param name="filter">Filter to use in </param>
/// <returns>Instance of T or null if not found</returns>
/// <exception cref="InvalidOperationException">The input sequence contains more than one element.-or-The input sequence is empty.</exception>
T GetService<T>(Predicate<T> filter);
/// <summary>
/// Gets multiple services of a given type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>An enumerable of matching services</returns>
IEnumerable<T> GetServices<T>();
/// <summary>
/// Gets multiple services of a given type, where they match a filter
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="filter"></param>
/// <returns></returns>
IEnumerable<T> GetServices<T>(Predicate<T> filter);
}
public abstract class ServiceProviderBase : IMultiServiceProvider
{
public T GetService<T>()
{
return GetServices<T>().SingleOrDefault();
}
public T GetService<T>(Predicate<T> filter)
{
Validate.IsNotNull(nameof(filter), filter);
return GetServices<T>().Where(t => filter(t)).SingleOrDefault();
}
public IEnumerable<T> GetServices<T>(Predicate<T> filter)
{
Validate.IsNotNull(nameof(filter), filter);
return GetServices<T>().Where(t => filter(t));
}
public virtual IEnumerable<T> GetServices<T>()
{
var services = GetServicesImpl<T>();
if (services == null)
{
return Enumerable.Empty<T>();
}
return services.Select(t =>
{
InitComposableService(t);
return t;
});
}
private void InitComposableService<T>(T t)
{
IComposableService c = t as IComposableService;
if (c != null)
{
c.SetServiceProvider(this);
}
}
/// <summary>
/// Gets all services using the build in implementation
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
protected abstract IEnumerable<T> GetServicesImpl<T>();
}
}

View File

@@ -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
{
/// <summary>
/// A service provider implementation that allows registering of specific services
/// </summary>
public class RegisteredServiceProvider : ServiceProviderBase
{
public delegate IEnumerable ServiceLookup();
protected Dictionary<Type, ServiceLookup> services = new Dictionary<Type, ServiceLookup>();
/// <summary>
/// Registers a singular service to be returned during lookup
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>this provider, to simplify fluent declarations</returns>
/// <exception cref="ArgumentNullException">If service is null</exception>
/// <exception cref="InvalidOperationException">If an existing service is already registered</exception>
public RegisteredServiceProvider RegisterSingleService<T>(T service)
{
Validate.IsNotNull(nameof(service), service);
ThrowIfAlreadyRegistered<T>();
services.Add(typeof(T), () => service.SingleItemAsEnumerable());
return this;
}
/// <summary>
/// Registers a singular service to be returned during lookup
/// </summary>
/// <param name="type">
/// Type or interface this service should be registed as. Any <see cref="IMultiServiceProvider.GetServices{T}"/> request
/// for that type will return this service
/// </param>
/// <param name="service">service object to be added</param>
/// <returns>this provider, to simplify fluent declarations</returns>
/// <exception cref="ArgumentNullException">If service is null</exception>
/// <exception cref="InvalidOperationException">If an existing service is already registered</exception>
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;
}
/// <summary>
/// Registers a function that can look up multiple services
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>this provider, to simplify fluent declarations</returns>
/// <exception cref="ArgumentNullException">If <paramref name="serviceLookup"/> is null</exception>
/// <exception cref="InvalidOperationException">If an existing service is already registered</exception>
public RegisteredServiceProvider Register<T>(Func<IEnumerable<T>> serviceLookup)
{
Validate.IsNotNull(nameof(serviceLookup), serviceLookup);
ThrowIfAlreadyRegistered<T>();
services.Add(typeof(T), () => serviceLookup());
return this;
}
private void ThrowIfAlreadyRegistered<T>()
{
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<T> GetServicesImpl<T>()
{
ServiceLookup serviceLookup;
if (services.TryGetValue(typeof(T), out serviceLookup))
{
return serviceLookup().Cast<T>();
}
return Enumerable.Empty<T>();
}
}
}

View File

@@ -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
{
/// <summary>
/// A formatting request to process an entire document
/// </summary>
public class DocumentFormattingRequest
{
public static readonly
RequestType<DocumentFormattingParams, TextEdit[]> Type =
RequestType<DocumentFormattingParams, TextEdit[]>.Create("textDocument/formatting");
}
/// <summary>
/// A formatting request to process a specific range inside a document
/// </summary>
public class DocumentRangeFormattingRequest
{
public static readonly
RequestType<DocumentRangeFormattingParams, TextEdit[]> Type =
RequestType<DocumentRangeFormattingParams, TextEdit[]>.Create("textDocument/rangeFormatting");
}
/// <summary>
/// A formatting request to handle a user typing, giving a chance to update the text based on this
/// </summary>
public class DocumentOnTypeFormattingRequest
{
public static readonly
RequestType<DocumentOnTypeFormattingParams, TextEdit[]> Type =
RequestType<DocumentOnTypeFormattingParams, TextEdit[]>.Create("textDocument/onTypeFormatting");
}
/// <summary>
/// Params for the <see cref="DocumentFormattingRequest"/>
/// </summary>
public class DocumentFormattingParams
{
/// <summary>
/// The document to format.
/// </summary>
public TextDocumentIdentifier TextDocument { get; set; }
/// <summary>
/// The formatting options
/// </summary>
public FormattingOptions Options { get; set; }
}
/// <summary>
/// Params for the <see cref="DocumentRangeFormattingRequest"/>
/// </summary>
public class DocumentRangeFormattingParams : DocumentFormattingParams
{
/// <summary>
/// The range to format
/// </summary>
public Range Range { get; set; }
}
/// <summary>
/// Params for the <see cref="DocumentOnTypeFormattingRequest"/>
/// </summary>
public class DocumentOnTypeFormattingParams : DocumentFormattingParams
{
/// <summary>
/// The position at which this request was sent.
/// </summary>
Position Position { get; set; }
/// <summary>
/// The character that has been typed.
/// </summary>
string Ch { get; set; }
}
/// <summary>
/// Value-object describing what options formatting should use.
/// </summary>
public class FormattingOptions
{
/// <summary>
/// Size of a tab in spaces
/// </summary>
public int TabSize { get; set; }
/// <summary>
/// Prefer spaces over tabs.
/// </summary>
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
}
}

View File

@@ -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
{
/// <summary>
/// Formats the text for a specific node.
/// </summary>
public abstract void Format();
internal static LexLocation GetLexLocationForNode(SqlCodeObject obj)
{
return obj.Position;
}
}
}

View File

@@ -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<T> : 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))
);
}
/// <summary>
/// Logical aliases for ProcessTokenRange that indicates the starting region is to be analyzed
/// </summary>
internal virtual void ProcessPrefixRegion(int startTokenNumber, int firstChildStartTokenNumber)
{
ProcessTokenRange(startTokenNumber, firstChildStartTokenNumber);
}
/// <summary>
/// Logical aliases for ProcessTokenRange that indicates the end region is to be analyzed
/// </summary>
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();
}
/// <summary>
/// Finds an expected token
/// </summary>
/// <param name="currentIndex">Current index to start the search at</param>
/// <param name="id">ID defining the type of token being looked for - e.g. parenthesis, INSERT</param>
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);
}
}

View File

@@ -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<T> : 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);
}
}

View File

@@ -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<SqlCodeObject>
{
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, "", ","));
}
}
}
}

View File

@@ -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<int> KeywordIdentifiers { get; set; }
private void LoadKeywordIdentifiers()
{
KeywordIdentifiers = new HashSet<int>();
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;
}
/// <summary>
/// Will apply any token-level formatting (e.g., uppercase/lowercase of keywords).
/// </summary>
[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<PaddedSpaceSeparatedListFormatter.ColumnSpacingFormatDefinition> CurrentColumnSpacingFormatDefinitions { get; set; }
}
}

View File

@@ -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)
{
}
}
}

View File

@@ -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 };
/// <summary>
/// The supported options to use when formatting text
/// </summary>
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;
}
}
}

View File

@@ -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
{
/// <summary>
/// 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
/// </summary>
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");
}
}

View File

@@ -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<String> 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();
}
}
}
}

View File

@@ -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
{
/// <summary>
/// The main entry point for our formatter implementation, via the <see cref="Format(string, FormatOptions, bool, Replacement.OnReplace)"/> 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 <see cref="SqlCodeObjectVisitor"/> 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.
/// </summary>
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>(T codeObject) where T : SqlCodeObject
{
ASTNodeFormatter f = GetFormatter(codeObject);
f.Format();
}
private ASTNodeFormatter GetFormatter<T>(T codeObject) where T:SqlCodeObject
{
Type astType = typeof(T);
ASTNodeFormatter formatter;
var formatterFactory = serviceProvider.GetServices<ASTNodeFormatterFactory>().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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="script1">SQL script containing the first token stream.</param>
/// <param name="script2">SQL script containing the second token stream.</param>
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);
}
}
}
}
}

View File

@@ -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); }
}
}

View File

@@ -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<SqlCodeObject>
{
public NoOpFormatter(FormatterVisitor visitor, SqlCodeObject codeObject)
: base(visitor, codeObject)
{
}
}
}

View File

@@ -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<ColumnSpacingFormatDefinition> ColumnSpacingDefinitions { get; set; }
private int nextColumn = 0;
internal PaddedSpaceSeparatedListFormatter(FormatterVisitor visitor, SqlCodeObject codeObject, List<ColumnSpacingFormatDefinition> 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; }
}
}
}

View File

@@ -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
{
/// <summary>
/// Describes a string editing action which requests that a particular
/// substring found at a given location be replaced by another string
/// </summary>
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;
}
}
/// <summary>
/// Checks whether the replacement will have any effect.
/// </summary>
/// <returns></returns>
internal bool IsIdentity()
{
return OldValue.Equals(NewValue);
}
/// <summary>
/// Reports the relative change in text length (number of characters)
/// between the initial and the formatted code introduced by this
/// particular replacement.
/// </summary>
public int InducedOffset
{
get
{
return NewValue.Length - OldValue.Length;
}
}
/// <summary>
/// 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.
/// </summary>
public int CumulativeOffset { set; private get; }
/// <summary>
/// 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.
/// </summary>
/// <param name="pos">Position of the begining of the replacement relative to the beginig of the character stream.</param>
/// <param name="len">The number of consecutive characters which are to be replaced.</param>
/// <param name="with">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.</param>
public delegate void OnReplace(int pos, int len, string with);
/// <summary>
/// Applies a replacement action according to a given strategy defined by the delegate procedure.
/// </summary>
/// <param name="replace">This delegate function implements the strategy for applying the replacement.</param>
public void Apply(OnReplace replace)
{
replace(StartIndex + CumulativeOffset, OldValue.Length, NewValue);
}
}
}

View File

@@ -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<Replacement> Replacements { get; set; }
public ReplacementQueue()
{
Replacements = new Queue<Replacement>();
}
/// <summary>
/// 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.
///
/// </summary>
/// <param name="r">The latest replacement to be added to the queue.</param>
public void Add(Replacement r)
{
if (!r.IsIdentity())
{
r.CumulativeOffset = offset;
Replacements.Enqueue(r);
offset += r.InducedOffset;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return Replacements.GetEnumerator();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<SqlBatch>
{
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);
}
}
}
}

View File

@@ -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<SqlBinaryBooleanExpression>
{
protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlBinaryBooleanExpression codeObject)
{
return new SqlBinaryBooleanExpressionFormatter(visitor, codeObject);
}
}
internal class SqlBinaryBooleanExpressionFormatter : ASTNodeFormatterT<SqlBinaryBooleanExpression>
{
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);
}
}
}

View File

@@ -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<SqlBinaryQueryExpression>
{
protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlBinaryQueryExpression codeObject)
{
return new SqlBinaryQueryExpressionFormatter(visitor, codeObject);
}
}
class SqlBinaryQueryExpressionFormatter : ASTNodeFormatterT<SqlBinaryQueryExpression>
{
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);
}
}
}

View File

@@ -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<SqlColumnDefinition>
{
protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlColumnDefinition codeObject)
{
return new SqlColumnDefinitionFormatter(visitor, codeObject);
}
}
internal class SqlColumnDefinitionFormatter : ASTNodeFormatterT<SqlColumnDefinition>
{
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);
}
}
}

View File

@@ -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<SqlCommonTableExpression>
{
protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlCommonTableExpression codeObject)
{
return new SqlCommonTableExpressionFormatter(visitor, codeObject);
}
}
internal class SqlCommonTableExpressionFormatter : SysCommentsFormatterBase<SqlCommonTableExpression>
{
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;
}
}
}

View File

@@ -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<SqlCompoundStatement>
{
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);
}
}
}
}

View File

@@ -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<SqlCreateProcedureStatement>
{
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)
{
}
}
}

View File

@@ -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<SqlCreateTableStatement>
{
protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlCreateTableStatement codeObject)
{
return new SqlCreateTableStatementFormatter(visitor, codeObject);
}
}
internal class SqlCreateTableStatementFormatter : ASTNodeFormatterT<SqlCreateTableStatement>
{
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);
}
}
}
}

View File

@@ -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<SqlDataType>
{
protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlDataType codeObject)
{
return new SqlDataTypeFormatter(visitor, codeObject);
}
}
internal class SqlDataTypeFormatter : ASTNodeFormatterT<SqlDataType>
{
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();
}
}
}
}

View File

@@ -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<SqlFromClause>
{
protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlFromClause codeObject)
{
return new SqlFromClauseFormatter(visitor, codeObject);
}
}
internal class SqlFromClauseFormatter : ASTNodeFormatterT<SqlFromClause>
{
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);
}
}
}

View File

@@ -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<SqlInsertSpecification>
{
protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlInsertSpecification codeObject)
{
return new SqlInsertSpecificationFormatter(visitor, codeObject);
}
}
class SqlInsertSpecificationFormatter : ASTNodeFormatterT<SqlInsertSpecification>
{
internal SqlInsertSpecificationFormatter(FormatterVisitor visitor, SqlInsertSpecification codeObject)
: base(visitor, codeObject)
{
}
public override void Format()
{
IEnumerator<SqlCodeObject> 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;
}
}
}

View File

@@ -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<SqlInsertStatement>
{
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)
{
}
}
}

View File

@@ -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<SqlProcedureDefinition>
{
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);
}
}
}
}

View File

@@ -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<SqlQualifiedJoinTableExpression>
{
protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlQualifiedJoinTableExpression codeObject)
{
return new SqlQualifiedJoinTableExpressionFormatter(visitor, codeObject);
}
}
internal class SqlQualifiedJoinTableExpressionFormatter : ASTNodeFormatterT<SqlQualifiedJoinTableExpression>
{
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);
}
}
}

View File

@@ -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<SqlQuerySpecification>
{
protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlQuerySpecification codeObject)
{
return new SqlQuerySpecificationFormatter(visitor, codeObject);
}
}
internal class SqlQuerySpecificationFormatter : ASTNodeFormatterT<SqlQuerySpecification>
{
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);
}
}
}

View File

@@ -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<SqlQueryWithClause>
{
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)
{
}
}
}

View File

@@ -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<SqlRowConstructorExpression>
{
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)
{
}
}
}

View File

@@ -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<SqlSelectClause>
{
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);
}
}
}
}

View File

@@ -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<SqlSelectSpecification>
{
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);
}
}
}
}

View File

@@ -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<SqlSelectStatement>
{
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)
{
}
}
}

View File

@@ -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<SqlTableConstructorExpression>
{
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)
{
}
}
}

View File

@@ -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<SqlTableDefinition>
{
protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlTableDefinition codeObject)
{
return new SqlTableDefinitionFormatter(visitor, codeObject);
}
}
[Export(typeof(ASTNodeFormatter))]
internal class SqlTableDefinitionFormatter : ASTNodeFormatterT<SqlTableDefinition>
{
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<PaddedSpaceSeparatedListFormatter.ColumnSpacingFormatDefinition> columnSpacingFormatDefinitions = new List<PaddedSpaceSeparatedListFormatter.ColumnSpacingFormatDefinition>(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);
}
}
}

View File

@@ -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<SqlViewDefinition>
{
protected override ASTNodeFormatter DoCreate(FormatterVisitor visitor, SqlViewDefinition codeObject)
{
return new SqlViewDefinitionFormatter(visitor, codeObject);
}
}
class SqlViewDefinitionFormatter : SysCommentsFormatterBase<SqlViewDefinition>
{
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<SqlModuleOption> 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);
}
}
}

View File

@@ -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
{
/// <summary>
/// Common base class for objects dealing with sys comments. These follow
/// similar patterns so identical methods are held here
/// </summary>
internal abstract class SysCommentsFormatterBase<T> : ASTNodeFormatterT<T>
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);
}
/// <summary>
/// processes any section in a query, since the basic behavior is constant
/// </summary>
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<int, int> 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);
}
/// <summary>
/// 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
/// </summary>
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<SqlIdentifier> 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;
}
}
}

View File

@@ -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
{
/// <summary>
/// Base class for a set of utility formatters that are used by Node-specific formatters when dealing with whitespace
/// </summary>
internal abstract class WhiteSpaceSeparatedListFormatter : ASTNodeFormatterT<SqlCodeObject>
{
private bool IncremenetIndentLevelOnPrefixRegion { get; set; }
/// <summary>
/// This constructor initalizes the <see cref="Visitor"/> and <see cref="CodeObject"/> properties since the formatter's entry point
/// is not the Format method
/// </summary>
/// <param name="visitor"></param>
/// <param name="codeObject"></param>
/// <param name="incrementIndentLevelOnPrefixRegion"></param>
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);
}
}

View File

@@ -0,0 +1,266 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Composition;
using System.IO;
using System.Threading.Tasks;
using Microsoft.SqlServer.Management.SqlParser.Parser;
using Microsoft.SqlTools.ServiceLayer.Extensibility;
using Microsoft.SqlTools.ServiceLayer.Formatter.Contracts;
using Microsoft.SqlTools.ServiceLayer.Hosting;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Formatter
{
[Export(typeof(IHostedService))]
public class TSqlFormatterService : HostedService<TSqlFormatterService>, IComposableService
{
private FormatterSettings settings;
/// <summary>
/// The default constructor is required for MEF-based composable services
/// </summary>
public TSqlFormatterService()
{
settings = new FormatterSettings();
}
public override void InitializeService(IProtocolEndpoint serviceHost)
{
serviceHost.SetRequestHandler(DocumentFormattingRequest.Type, HandleDocFormatRequest);
serviceHost.SetRequestHandler(DocumentRangeFormattingRequest.Type, HandleDocRangeFormatRequest);
WorkspaceService?.RegisterConfigChangeCallback(HandleDidChangeConfigurationNotification);
}
/// <summary>
/// Gets the workspace service. Note: should handle case where this is null in cases where unit tests do not set this up
/// </summary>
private WorkspaceService<SqlToolsSettings> WorkspaceService
{
get { return ServiceProvider.GetService<WorkspaceService<SqlToolsSettings>>(); }
}
/// <summary>
/// Ensure formatter settings are always up to date
/// </summary>
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<TextEdit[]> requestContext)
{
Func<Task<TextEdit[]>> requestHandler = () =>
{
return FormatAndReturnEdits(docFormatParams);
};
await HandleRequest(requestHandler, requestContext, "HandleDocFormatRequest");
}
public async Task HandleDocRangeFormatRequest(DocumentRangeFormattingParams docRangeFormatParams, RequestContext<TextEdit[]> requestContext)
{
Func<Task<TextEdit[]>> requestHandler = () =>
{
return FormatRangeAndReturnEdits(docRangeFormatParams);
};
await HandleRequest(requestHandler, requestContext, "HandleDocRangeFormatRequest");
}
private async Task<TextEdit[]> 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<TextEdit[]> 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<TextEdit> edits = new List<TextEdit>();
edit.NewText = Format(text, options, false);
// TODO do not add if no formatting needed?
edits.Add(edit);
return edits.ToArray();
}
private FormatOptions GetOptions(DocumentFormattingParams docFormatParams)
{
FormatOptions options = new FormatOptions();
if (docFormatParams.Options != null)
{
options.UseSpaces = docFormatParams.Options.InsertSpaces;
options.SpacesPerIndent = docFormatParams.Options.TabSize;
}
if (settings != null)
{
if (settings.AlignColumnDefinitionsInColumns.HasValue) { options.AlignColumnDefinitionsInColumns = settings.AlignColumnDefinitionsInColumns.Value; }
if (settings.PlaceCommasBeforeNextStatement.HasValue) { options.PlaceCommasBeforeNextStatement = settings.PlaceCommasBeforeNextStatement.Value; }
if (settings.PlaceSelectStatementReferencesOnNewLine.HasValue) { options.PlaceEachReferenceOnNewLineInQueryStatements = settings.PlaceSelectStatementReferencesOnNewLine.Value; }
if (settings.UseBracketForIdentifiers.HasValue) { options.EncloseIdentifiersInSquareBrackets = settings.UseBracketForIdentifiers.Value; }
}
return options;
}
internal static void UpdateFormatOptionsFromSettings(FormatOptions options, FormatterSettings settings)
{
Validate.IsNotNull(nameof(options), options);
if (settings != null)
{
if (settings.AlignColumnDefinitionsInColumns.HasValue) { options.AlignColumnDefinitionsInColumns = settings.AlignColumnDefinitionsInColumns.Value; }
if (settings.PlaceCommasBeforeNextStatement.HasValue) { options.PlaceCommasBeforeNextStatement = settings.PlaceCommasBeforeNextStatement.Value; }
if (settings.PlaceSelectStatementReferencesOnNewLine.HasValue) { options.PlaceEachReferenceOnNewLineInQueryStatements = settings.PlaceSelectStatementReferencesOnNewLine.Value; }
if (settings.UseBracketForIdentifiers.HasValue) { options.EncloseIdentifiersInSquareBrackets = settings.UseBracketForIdentifiers.Value; }
options.DatatypeCasing = settings.DatatypeCasing;
options.KeywordCasing = settings.KeywordCasing;
}
}
private ScriptFile GetFile(DocumentFormattingParams docFormatParams)
{
return WorkspaceService.Workspace.GetFile(docFormatParams.TextDocument.Uri);
}
private static TextEdit PrepareEdit(ScriptFile scriptFile)
{
int fileLines = scriptFile.FileLines.Count;
Position start = new Position { Line = 0, Character = 0 };
int lastChar = scriptFile.FileLines[scriptFile.FileLines.Count - 1].Length;
Position end = new Position { Line = scriptFile.FileLines.Count - 1, Character = lastChar };
TextEdit edit = new TextEdit
{
Range = new Range { Start = start, End = end }
};
return edit;
}
private async Task HandleRequest<T>(Func<Task<T>> handler, RequestContext<T> 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<FormatterVisitor> 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)
);
}
}
}

View File

@@ -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
{
/// <summary>
/// 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
/// </summary>
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<SqlToolsSettings>.Instance.InitializeService(serviceHost);
serviceProvider.RegisterSingleService(WorkspaceService<SqlToolsSettings>.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();
}
/// <summary>
/// Internal to support testing. Initializes <see cref="IHostedService"/> instances in the service,
/// and registers them for their preferred service type
/// </summary>
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<IHostedService>())
{
provider.RegisterSingleService(service.ServiceType, service);
}
foreach (IHostedService service in provider.GetServices<IHostedService>())
{
// 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);
}
}
}
}

View File

@@ -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; }

View File

@@ -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
{
/// <summary>
/// Defines a hosted service that communicates with external processes via
/// messages passed over the <see cref="ServiceHost"/>. The service defines
/// a standard initialization method where it can hook up to the host.
/// </summary>
public interface IHostedService
{
/// <summary>
/// Callback to initialize this service
/// </summary>
/// <param name="serviceHost"><see cref="IProtocolEndpoint"/> which supports registering
/// event handlers and other callbacks for messages passed to external callers</param>
void InitializeService(IProtocolEndpoint serviceHost);
/// <summary>
/// What is the service type that you wish to register?
/// </summary>
Type ServiceType { get; }
}
/// <summary>
/// Base class for <see cref="IHostedService"/> implementations that handles defining the <see cref="ServiceType"/>
/// being registered. This simplifies service registration. This also implements <see cref="IComposableService"/> which
/// allows injection of the service provider for lookup of other services.
///
/// Extending classes should implement per below code example
/// <code>
/// [Export(typeof(IHostedService)]
/// MyService : HostedService&lt;MyService&gt;
/// {
/// public override void InitializeService(IProtocolEndpoint serviceHost)
/// {
/// serviceHost.SetRequestHandler(MyRequest.Type, HandleMyRequest);
/// }
/// }
/// </code>
/// </summary>
/// <typeparam name="T">Type to be registered for lookup in the service provider</typeparam>
public abstract class HostedService<T> : 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);
}
}

View File

@@ -19,6 +19,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
/// </summary>
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
}
/// <summary>
/// Starts the language server client and sends the Initialize method.
/// Initializes
/// </summary>
/// <returns>A Task that can be awaited for initialization to complete.</returns>
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;
}
}
/// <summary>
/// Starts the language server client and sends the Initialize method.
/// </summary>
/// <returns>A Task that can be awaited for initialization to complete.</returns>
public async Task Start()
{
if (!this.isStarted)
{
// Notify implementation about endpoint start
await this.OnStart();

View File

@@ -58,7 +58,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
/// <summary>
/// Provide initialization that must occur after the service host is started
/// </summary>
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

View File

@@ -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
{

View File

@@ -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; }
}
}

View File

@@ -4,11 +4,6 @@
using System;
using Microsoft.SqlTools.ServiceLayer.Hosting;
using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.Credentials;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer
@@ -36,25 +31,13 @@ namespace Microsoft.SqlTools.ServiceLayer
Logger.Write(LogLevel.Normal, "Starting SQL Tools Service Host");
// set up the host details and profile paths
var hostDetails = new HostDetails(version: new Version(1,0));
var hostDetails = new HostDetails(version: new Version(1, 0));
SqlToolsContext sqlToolsContext = new SqlToolsContext(hostDetails);
ServiceHost serviceHost = HostLoader.CreateAndStartServiceHost(sqlToolsContext);
// Grab the instance of the service host
ServiceHost serviceHost = ServiceHost.Instance;
// Start the service
serviceHost.Start().Wait();
// Initialize the services that will be hosted here
WorkspaceService<SqlToolsSettings>.Instance.InitializeService(serviceHost);
LanguageService.Instance.InitializeService(serviceHost, sqlToolsContext);
ConnectionService.Instance.InitializeService(serviceHost);
CredentialService.Instance.InitializeService(serviceHost);
QueryExecutionService.Instance.InitializeService(serviceHost);
serviceHost.Initialize();
serviceHost.WaitForExit();
}
}
}

View File

@@ -0,0 +1,88 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.ServiceLayer.Formatter;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Microsoft.SqlTools.ServiceLayer.SqlContext
{
/// <summary>
/// Contract for receiving formatter-specific settings as part of workspace settings
/// </summary>
public class FormatterSettings
{
/// <summary>
/// Should names be escaped, for example converting dbo.T1 to [dbo].[T1]
/// </summary>
public bool? UseBracketForIdentifiers
{
get;
set;
}
/// <summary>
/// Should comma separated lists have the comma be at the start of a new line.
/// <code>
/// CREATE TABLE T1 (
/// C1 INT
/// , C2 INT)
/// </code>
/// </summary>
public bool? PlaceCommasBeforeNextStatement
{
get;
set;
}
/// <summary>
/// Should each reference be on its own line or should references to multiple objects
/// be kept on a single line
/// <code>
/// SELECT *
/// FROM T1,
/// T2
/// </code>
/// </summary>
public bool? PlaceSelectStatementReferencesOnNewLine
{
get;
set;
}
/// <summary>
/// Should keyword casing be ignored, converted to all uppercase, or
/// converted to all lowercase
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public CasingOptions KeywordCasing
{
get;
set;
}
/// <summary>
/// Should data type casing be ignored, converted to all uppercase, or
/// converted to all lowercase
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public CasingOptions DatatypeCasing
{
get;
set;
}
/// <summary>
/// Should column definitions be aligned or left non-aligned?
/// </summary>
public bool? AlignColumnDefinitionsInColumns
{
get;
set;
}
}
}

View File

@@ -113,9 +113,9 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
/// </summary>
public SqlToolsSettingsValues()
{
this.IntelliSense = new IntelliSenseSettings();
this.QueryExecutionSettings = new QueryExecutionSettings();
IntelliSense = new IntelliSenseSettings();
QueryExecutionSettings = new QueryExecutionSettings();
Format = new FormatterSettings();
}
/// <summary>
@@ -127,5 +127,11 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
/// Gets or sets the query execution settings
/// </summary>
public QueryExecutionSettings QueryExecutionSettings { get; set; }
/// <summary>
/// Gets or sets the formatter settings
/// </summary>
[JsonProperty("format")]
public FormatterSettings Format { get; set; }
}
}

View File

@@ -63,7 +63,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
/// <returns>A new FilePosition instance for the calculated position.</returns>
public FilePosition AddOffset(int lineOffset, int columnOffset)
{
return this.scriptFile.CalculatePosition(
return scriptFile.CalculatePosition(
this,
lineOffset,
columnOffset);
@@ -77,7 +77,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
/// <returns>A new FilePosition instance for the calculated position.</returns>
public FilePosition GetLineStart()
{
string scriptLine = scriptFile.FileLines[this.Line - 1];
string scriptLine = scriptFile.FileLines[Line - 1];
int lineStartColumn = 1;
for (int i = 0; i < scriptLine.Length; i++)
@@ -89,7 +89,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
}
}
return new FilePosition(this.scriptFile, this.Line, lineStartColumn);
return new FilePosition(scriptFile, Line, lineStartColumn);
}
/// <summary>
@@ -99,8 +99,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
/// <returns>A new FilePosition instance for the calculated position.</returns>
public FilePosition GetLineEnd()
{
string scriptLine = scriptFile.FileLines[this.Line - 1];
return new FilePosition(this.scriptFile, this.Line, scriptLine.Length + 1);
string scriptLine = scriptFile.FileLines[Line - 1];
return new FilePosition(scriptFile, Line, scriptLine.Length + 1);
}
#endregion

View File

@@ -61,11 +61,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
{
get
{
return string.Join("\r\n", this.FileLines);
return string.Join("\r\n", FileLines);
}
set
{
this.FileLines = value != null ? value.Split('\n') : null;
FileLines = value != null ? value.Split('\n') : null;
}
}
@@ -118,12 +118,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
string clientFilePath,
TextReader textReader)
{
this.FilePath = filePath;
this.ClientFilePath = clientFilePath;
this.IsAnalysisEnabled = true;
this.IsInMemory = Workspace.IsPathInMemory(filePath);
FilePath = filePath;
ClientFilePath = clientFilePath;
IsAnalysisEnabled = true;
IsInMemory = Workspace.IsPathInMemory(filePath);
this.SetFileContents(textReader.ReadToEnd());
SetFileContents(textReader.ReadToEnd());
}
/// <summary>
@@ -137,11 +137,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
string clientFilePath,
string initialBuffer)
{
this.FilePath = filePath;
this.ClientFilePath = clientFilePath;
this.IsAnalysisEnabled = true;
FilePath = filePath;
ClientFilePath = clientFilePath;
IsAnalysisEnabled = true;
this.SetFileContents(initialBuffer);
SetFileContents(initialBuffer);
}
#endregion
@@ -157,9 +157,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
{
Validate.IsWithinRange(
"lineNumber", lineNumber,
1, this.FileLines.Count + 1);
1, FileLines.Count + 1);
return this.FileLines[lineNumber - 1];
return FileLines[lineNumber - 1];
}
/// <summary>
/// Gets the text under a specific range
/// </summary>
public string GetTextInRange(BufferRange range)
{
return string.Join(Environment.NewLine, GetLinesInRange(range));
}
/// <summary>
@@ -170,8 +178,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
/// <returns>An array of strings from the specified range of the file.</returns>
public virtual string[] GetLinesInRange(BufferRange bufferRange)
{
this.ValidatePosition(bufferRange.Start);
this.ValidatePosition(bufferRange.End);
ValidatePosition(bufferRange.Start);
ValidatePosition(bufferRange.End);
List<string> linesInRange = new List<string>();
@@ -180,7 +188,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
for (int line = startLine; line <= endLine; line++)
{
string currentLine = this.FileLines[line - 1];
string currentLine = FileLines[line - 1];
int startColumn =
line == startLine
? bufferRange.Start.Column
@@ -208,7 +216,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
/// <param name="bufferPosition">The position in the buffer to be validated.</param>
public void ValidatePosition(BufferPosition bufferPosition)
{
this.ValidatePosition(
ValidatePosition(
bufferPosition.Line,
bufferPosition.Column);
}
@@ -221,14 +229,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
/// <param name="column">The 1-based column to be validated.</param>
public void ValidatePosition(int line, int column)
{
if (line < 1 || line > this.FileLines.Count + 1)
if (line < 1 || line > FileLines.Count + 1)
{
throw new ArgumentOutOfRangeException(nameof(line), SR.WorkspaceServicePositionLineOutOfRange);
}
// The maximum column is either one past the length of the string
// or 1 if the string is empty.
string lineString = this.FileLines[line - 1];
string lineString = FileLines[line - 1];
int maxColumn = lineString.Length > 0 ? lineString.Length + 1 : 1;
if (column < 1 || column > maxColumn)
@@ -243,28 +251,28 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
/// <param name="fileChange">The FileChange to apply to the file's contents.</param>
public void ApplyChange(FileChange fileChange)
{
this.ValidatePosition(fileChange.Line, fileChange.Offset);
this.ValidatePosition(fileChange.EndLine, fileChange.EndOffset);
ValidatePosition(fileChange.Line, fileChange.Offset);
ValidatePosition(fileChange.EndLine, fileChange.EndOffset);
// Break up the change lines
string[] changeLines = fileChange.InsertString.Split('\n');
// Get the first fragment of the first line
string firstLineFragment =
this.FileLines[fileChange.Line - 1]
FileLines[fileChange.Line - 1]
.Substring(0, fileChange.Offset - 1);
// Get the last fragment of the last line
string endLine = this.FileLines[fileChange.EndLine - 1];
string endLine = FileLines[fileChange.EndLine - 1];
string lastLineFragment =
endLine.Substring(
fileChange.EndOffset - 1,
(this.FileLines[fileChange.EndLine - 1].Length - fileChange.EndOffset) + 1);
(FileLines[fileChange.EndLine - 1].Length - fileChange.EndOffset) + 1);
// Remove the old lines
for (int i = 0; i <= fileChange.EndLine - fileChange.Line; i++)
{
this.FileLines.RemoveAt(fileChange.Line - 1);
FileLines.RemoveAt(fileChange.Line - 1);
}
// Build and insert the new lines
@@ -287,7 +295,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
finalLine = finalLine + lastLineFragment;
}
this.FileLines.Insert(currentLineNumber - 1, finalLine);
FileLines.Insert(currentLineNumber - 1, finalLine);
currentLineNumber++;
}
}
@@ -301,7 +309,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
/// <returns>The zero-based offset for the given file position.</returns>
public int GetOffsetAtPosition(int lineNumber, int columnNumber)
{
Validate.IsWithinRange("lineNumber", lineNumber, 1, this.FileLines.Count);
Validate.IsWithinRange("lineNumber", lineNumber, 1, FileLines.Count);
Validate.IsGreaterThan("columnNumber", columnNumber, 0);
int offset = 0;
@@ -316,7 +324,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
else
{
// Add an offset to account for the current platform's newline characters
offset += this.FileLines[i].Length + Environment.NewLine.Length;
offset += FileLines[i].Length + Environment.NewLine.Length;
}
}
@@ -339,9 +347,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
int newLine = originalPosition.Line + lineOffset,
newColumn = originalPosition.Column + columnOffset;
this.ValidatePosition(newLine, newColumn);
ValidatePosition(newLine, newColumn);
string scriptLine = this.FileLines[newLine - 1];
string scriptLine = FileLines[newLine - 1];
newColumn = Math.Min(scriptLine.Length + 1, newColumn);
return new FilePosition(this, newLine, newColumn);
@@ -379,9 +387,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
BufferPosition endPosition = startPosition;
int line = 0;
while (line < this.FileLines.Count)
while (line < FileLines.Count)
{
if (searchedOffset <= currentOffset + this.FileLines[line].Length)
if (searchedOffset <= currentOffset + FileLines[line].Length)
{
int column = searchedOffset - currentOffset;
@@ -415,7 +423,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
}
// Increase the current offset and include newline length
currentOffset += this.FileLines[line].Length + Environment.NewLine.Length;
currentOffset += FileLines[line].Length + Environment.NewLine.Length;
line++;
}
@@ -430,7 +438,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace.Contracts
{
// Split the file contents into lines and trim
// any carriage returns from the strings.
this.FileLines =
FileLines =
fileContents
.Split('\n')
.Select(line => line.TrimEnd('\r'))

View File

@@ -28,8 +28,15 @@
"NETStandard.Library": "1.6.0",
"Microsoft.NETCore.Runtime.CoreCLR": "1.0.2",
"Microsoft.NETCore.DotNetHostPolicy": "1.0.1",
"Microsoft.DiaSymReader.Native": "1.4.1",
"System.Diagnostics.Process": "4.1.0",
"System.Threading.Thread": "4.0.0",
"System.Runtime.Loader": "4.0.0",
"System.Composition.Runtime": "1.0.31",
"System.Composition.Convention": "1.0.31",
"System.Composition.TypedParts": "1.0.31",
"Microsoft.Extensions.DependencyModel": "1.0.0",
"System.Runtime": "4.3.0",
"Microsoft.DiaSymReader.Native": "1.4.1"
},
"frameworks": {

View File

@@ -117,6 +117,62 @@ namespace Microsoft.SqlTools.ServiceLayer
}
}
public static string ServiceAlreadyRegistered
{
get
{
return Keys.GetString(Keys.ServiceAlreadyRegistered);
}
}
public static string MultipleServicesFound
{
get
{
return Keys.GetString(Keys.MultipleServicesFound);
}
}
public static string IncompatibleServiceForExtensionLoader
{
get
{
return Keys.GetString(Keys.IncompatibleServiceForExtensionLoader);
}
}
public static string ServiceProviderNotSet
{
get
{
return Keys.GetString(Keys.ServiceProviderNotSet);
}
}
public static string ServiceNotFound
{
get
{
return Keys.GetString(Keys.ServiceNotFound);
}
}
public static string ServiceNotOfExpectedType
{
get
{
return Keys.GetString(Keys.ServiceNotOfExpectedType);
}
}
public static string ErrorUnexpectedCodeObjectType
{
get
{
return Keys.GetString(Keys.ErrorUnexpectedCodeObjectType);
}
}
public static string HostingUnexpectedEndOfStream
{
get
@@ -413,6 +469,14 @@ namespace Microsoft.SqlTools.ServiceLayer
}
}
public static string ErrorEmptyStringReplacement
{
get
{
return Keys.GetString(Keys.ErrorEmptyStringReplacement);
}
}
public static string WorkspaceServicePositionLineOutOfRange
{
get
@@ -774,6 +838,27 @@ namespace Microsoft.SqlTools.ServiceLayer
public const string CredentialServiceWin32CredentialDisposed = "CredentialServiceWin32CredentialDisposed";
public const string ServiceAlreadyRegistered = "ServiceAlreadyRegistered";
public const string MultipleServicesFound = "MultipleServicesFound";
public const string IncompatibleServiceForExtensionLoader = "IncompatibleServiceForExtensionLoader";
public const string ServiceProviderNotSet = "ServiceProviderNotSet";
public const string ServiceNotFound = "ServiceNotFound";
public const string ServiceNotOfExpectedType = "ServiceNotOfExpectedType";
public const string ErrorUnexpectedCodeObjectType = "ErrorUnexpectedCodeObjectType";
public const string HostingUnexpectedEndOfStream = "HostingUnexpectedEndOfStream";
@@ -903,6 +988,9 @@ namespace Microsoft.SqlTools.ServiceLayer
public const string PeekDefinitionTypeNotSupportedError = "PeekDefinitionTypeNotSupportedError";
public const string ErrorEmptyStringReplacement = "ErrorEmptyStringReplacement";
public const string WorkspaceServicePositionLineOutOfRange = "WorkspaceServicePositionLineOutOfRange";

View File

@@ -181,6 +181,34 @@
<value>Win32Credential object is already disposed</value>
<comment></comment>
</data>
<data name="ServiceAlreadyRegistered" xml:space="preserve">
<value>Cannot register service for type {0}, one or more services already registered</value>
<comment></comment>
</data>
<data name="MultipleServicesFound" xml:space="preserve">
<value>Multiple services found for type {0}, expected only 1</value>
<comment></comment>
</data>
<data name="IncompatibleServiceForExtensionLoader" xml:space="preserve">
<value>Service of type {0} cannot be created by ExtensionLoader&lt;{1}&gt;</value>
<comment></comment>
</data>
<data name="ServiceProviderNotSet" xml:space="preserve">
<value>SetServiceProvider() was not called to establish the required service provider</value>
<comment></comment>
</data>
<data name="ServiceNotFound" xml:space="preserve">
<value>Service {0} was not found in the service provider</value>
<comment></comment>
</data>
<data name="ServiceNotOfExpectedType" xml:space="preserve">
<value>Service of Type {0} is not compatible with registered Type {1}</value>
<comment></comment>
</data>
<data name="ErrorUnexpectedCodeObjectType" xml:space="preserve">
<value>Cannot convert SqlCodeObject Type {0} to Type {1}</value>
<comment></comment>
</data>
<data name="HostingUnexpectedEndOfStream" xml:space="preserve">
<value>MessageReader's input stream ended unexpectedly, terminating</value>
<comment></comment>
@@ -359,6 +387,10 @@
<value>This object type is currently not supported by this feature.</value>
<comment></comment>
</data>
<data name="ErrorEmptyStringReplacement" xml:space="preserve">
<value>Replacement of an empty string by an empty string.</value>
<comment></comment>
</data>
<data name="WorkspaceServicePositionLineOutOfRange" xml:space="preserve">
<value>Position is outside of file line range</value>
<comment></comment>

View File

@@ -59,6 +59,21 @@ CredentialsServiceTargetForLookup = Target must be specified to check existance
CredentialServiceWin32CredentialDisposed = Win32Credential object is already disposed
############################################################################
# Extensibility
ServiceAlreadyRegistered = Cannot register service for type {0}, one or more services already registered
MultipleServicesFound = Multiple services found for type {0}, expected only 1
IncompatibleServiceForExtensionLoader = Service of type {0} cannot be created by ExtensionLoader<{1}>
ServiceProviderNotSet = SetServiceProvider() was not called to establish the required service provider
ServiceNotFound = Service {0} was not found in the service provider
ServiceNotOfExpectedType = Service of Type {0} is not compatible with registered Type {1}
############################################################################
# Formatter
ErrorUnexpectedCodeObjectType = Cannot convert SqlCodeObject Type {0} to Type {1}
############################################################################
# Hosting
@@ -168,6 +183,8 @@ PeekDefinitionTimedoutError = Operation timed out.
PeekDefinitionTypeNotSupportedError = This object type is currently not supported by this feature.
ErrorEmptyStringReplacement = Replacement of an empty string by an empty string.
############################################################################
# Workspace Service