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