Visitar URL original
Sync repos: Release 180.78.1 (#220) · codezerg/SqlScriptDOM@01aa17b · GitHub
Skip to content

Commit 01aa17b

Browse files
allancascanteAllan Cascante
andauthored
Sync repos: Release 180.78.1 (microsoft#220)
Co-authored-by: Allan Cascante <acascante@microsoft.com>
1 parent db6fe88 commit 01aa17b

26 files changed

Lines changed: 3047 additions & 65 deletions
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//------------------------------------------------------------------------------
2+
// <copyright file="ClauseBodyAlignment.cs" company="Microsoft">
3+
// Copyright (c) Microsoft Corporation. All rights reserved.
4+
// </copyright>
5+
//------------------------------------------------------------------------------
6+
7+
namespace Microsoft.SqlServer.TransactSql.ScriptDom
8+
{
9+
/// <summary>
10+
/// Represents how the body of a clause (the part after FROM, WHERE, GROUP BY, etc.) is laid out
11+
/// relative to its keyword.
12+
/// </summary>
13+
public enum ClauseBodyAlignment
14+
{
15+
/// <summary>
16+
/// Keep the body on the keyword's line and line all clause bodies up under a shared column
17+
/// past the widest keyword (the classic "rivers of whitespace" style).
18+
/// </summary>
19+
Aligned,
20+
21+
/// <summary>
22+
/// Put the body on its own new line, indented one level (<see cref="IndentationMode"/> /
23+
/// IndentationSize) past the keyword, so nesting grows one step per level instead of drifting
24+
/// right as keywords get wider. When this value is used, the AlignClauseBodies setting is
25+
/// ignored.
26+
/// </summary>
27+
Indented
28+
}
29+
}

‎SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/ScriptWriter.cs‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,44 @@ public void NewLine()
9393
}
9494
}
9595

96+
// Returns true if the most recently added meaningful token is a semicolon, ignoring any
97+
// trailing whitespace, newlines and alignment points. Used to avoid emitting a redundant
98+
// separating semicolon when the previous statement already ended with one.
99+
public Boolean LastMeaningfulTokenIsSemicolon()
100+
{
101+
for (Int32 index = _scriptWriterElements.Count - 1; index >= 0; --index)
102+
{
103+
ScriptWriterElement element = _scriptWriterElements[index];
104+
105+
if (element.ElementType == ScriptWriterElementType.NewLine ||
106+
element.ElementType == ScriptWriterElementType.AlignmentPoint)
107+
{
108+
continue;
109+
}
110+
111+
TokenWrapper tokenWrapper = element as TokenWrapper;
112+
if (tokenWrapper != null)
113+
{
114+
TSqlTokenType tokenType = tokenWrapper.Token.TokenType;
115+
116+
// Skip trailing comments so a semicolon emitted before them is still detected;
117+
// otherwise the caller would append the separator into the comment text.
118+
if (tokenType == TSqlTokenType.WhiteSpace ||
119+
tokenType == TSqlTokenType.SingleLineComment ||
120+
tokenType == TSqlTokenType.MultilineComment)
121+
{
122+
continue;
123+
}
124+
125+
return tokenType == TSqlTokenType.Semicolon;
126+
}
127+
128+
return false;
129+
}
130+
131+
return false;
132+
}
133+
96134
public void Indent(Int32 size)
97135
{
98136
AddSpace(size);

‎SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.CommonPhrases.cs‎

Lines changed: 110 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,11 +315,18 @@ protected void GenerateNewLineOrSpace(Boolean newline)
315315
}
316316
}
317317

318-
// mark an alignment point for clause body if it's configured so at a new line
318+
// Mark the shared cross-clause "river" alignment point for a clause body, when the current
319+
// options call for it.
320+
//
321+
// ClauseBodyAlignment.Indented never uses the river: a clause body either moves onto its own
322+
// indented line (see GenerateClauseBodyStart) or stays on the keyword line separated by a
323+
// single space. Marking in that mode would re-introduce the river padding the Indented layout
324+
// deliberately drops, so the mode is part of the guard below instead of being repeated by
325+
// every caller. Every other case - including the default (Aligned) - is unchanged.
319326
protected void MarkClauseBodyAlignmentWhenNecessary(Boolean newline, AlignmentPoint ap)
320327
{
321-
// If we didn't put a newline in, don't align, even if AlignClauseBodies is on
322-
if (newline && _options.AlignClauseBodies)
328+
// If we didn't put a newline in, don't align, even if AlignClauseBodies is on.
329+
if (newline && _options.AlignClauseBodies && _options.ClauseBodyAlignment != ClauseBodyAlignment.Indented)
323330
{
324331
#if !PIMODLANGUAGE
325332
Debug.Assert(ap != null, "Alignment point should not be null");
@@ -331,6 +338,29 @@ protected void MarkClauseBodyAlignmentWhenNecessary(Boolean newline, AlignmentPo
331338
}
332339
}
333340

341+
// Handle the transition from a clause keyword to its body when the body would start on a new
342+
// line (newline == true).
343+
//
344+
// Default (Aligned) mode: this is a pass-through - it marks the shared clause-body alignment
345+
// point exactly as before (via MarkClauseBodyAlignmentWhenNecessary) and returns false, so the
346+
// caller emits the usual separating space. The combination is identical to the original
347+
// "MarkClauseBodyAlignmentWhenNecessary(...); GenerateSpace();" pair, so the default output
348+
// does not change.
349+
//
350+
// Indented mode: the body is broken onto its own line, indented one level, and this returns
351+
// true so the caller skips the separating space.
352+
protected Boolean GenerateClauseBodyStart(Boolean newline, AlignmentPoint ap)
353+
{
354+
if (newline && _options.ClauseBodyAlignment == ClauseBodyAlignment.Indented)
355+
{
356+
NewLineAndIndent();
357+
return true;
358+
}
359+
360+
MarkClauseBodyAlignmentWhenNecessary(newline, ap);
361+
return false;
362+
}
363+
334364
protected void MarkInsertColumnsAlignmentPointWhenNecessary(AlignmentPoint ap)
335365
{
336366
#if !PIMODLANGUAGE
@@ -400,6 +430,53 @@ protected void GenerateQueryExpressionInParentheses(QueryExpression queryExpress
400430
GenerateSymbol(TSqlTokenType.RightParenthesis);
401431
}
402432

433+
// Emits the parameter list for a CREATE/ALTER PROCEDURE or FUNCTION statement.
434+
//
435+
// The default (MultilineProcedureParametersList == false) is intentionally the existing,
436+
// unchanged behavior: all parameters are written on a single line - function parameters in
437+
// parentheses, procedure parameters without. Multi-line output (one parameter per line,
438+
// indented one level from the procedure/function name) is strictly opt-in via the option.
439+
// CommaPlacement is honored by the underlying list generation when multi-line is enabled.
440+
protected void GenerateProcedureOrFunctionParameters(IList<ProcedureParameter> parameters, bool parenthesized)
441+
{
442+
bool hasParameters = parameters != null && parameters.Count > 0;
443+
444+
// Default path: unchanged single-line behavior. Taken whenever the option is off (its
445+
// default) or there is nothing to spread across multiple lines.
446+
if (!_options.MultilineProcedureParametersList || !hasParameters)
447+
{
448+
if (parenthesized)
449+
{
450+
NewLine();
451+
GenerateParenthesisedCommaSeparatedList(parameters);
452+
if (!hasParameters)
453+
{
454+
GenerateSymbol(TSqlTokenType.LeftParenthesis);
455+
GenerateSpaceAndSymbol(TSqlTokenType.RightParenthesis);
456+
}
457+
}
458+
else if (hasParameters)
459+
{
460+
NewLine();
461+
GenerateCommaSeparatedList(parameters);
462+
}
463+
464+
return;
465+
}
466+
467+
// Opt-in path: one parameter per line, indented one level.
468+
if (parenthesized)
469+
{
470+
ListGenerationOption option = ListGenerationOption.CreateOptionFromFormattingConfig(_options);
471+
GenerateFragmentList(parameters, option);
472+
}
473+
else
474+
{
475+
// The option produces its own leading new line before the first parameter.
476+
GenerateFragmentList(parameters, ListGenerationOption.MultipleLineProcedureParameterOption);
477+
}
478+
}
479+
403480
// True while rendering a SELECT projection list (QuerySpecification.SelectElements).
404481
// Restricts the "alias = expression" ColumnAliasStyle form to real SELECT projections,
405482
// because OUTPUT, OUTPUT INTO and RECEIVE reuse SelectScalarExpression but do not
@@ -508,6 +585,36 @@ protected void GenerateSemiColonWhenNecessary(TSqlStatement node)
508585
}
509586
}
510587

588+
// Some statements must be preceded by a semicolon terminator to be valid in SQL Server:
589+
// statements that begin with a WITH clause (common table expression / XMLNAMESPACES) and
590+
// the THROW statement. When such a statement follows a statement that was not already
591+
// terminated with a semicolon (for example an IF / BEGIN...END / WHILE / TRY...CATCH block,
592+
// whose generated form ends with END and no terminator), the required separating semicolon
593+
// is emitted so the generated script is valid for SQL Server, which enforces the terminator
594+
// even though ScriptDom's own parser is lenient.
595+
protected void GenerateSeparatingSemiColonWhenNecessary(TSqlStatement previous, TSqlStatement next)
596+
{
597+
if (previous != null &&
598+
next != null &&
599+
_generateSemiColon &&
600+
StatementRequiresPrecedingSemiColon(next) &&
601+
_writer.LastMeaningfulTokenIsSemicolon() == false)
602+
{
603+
GenerateSymbol(TSqlTokenType.Semicolon);
604+
}
605+
}
606+
607+
private static Boolean StatementRequiresPrecedingSemiColon(TSqlStatement statement)
608+
{
609+
if (statement is ThrowStatement)
610+
{
611+
return true;
612+
}
613+
614+
StatementWithCtesAndXmlNamespaces statementWithCtes = statement as StatementWithCtesAndXmlNamespaces;
615+
return statementWithCtes != null && statementWithCtes.WithCtesAndXmlNamespaces != null;
616+
}
617+
511618
/// <summary>
512619
/// Generates a statement fragment with semicolon placed before any trailing comments.
513620
/// This prevents semicolons from being appended after single-line comments (-- style),

‎SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.ExternalFunctionStatement.cs‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,18 @@ private void GenerateExternalFunctionStatementBody(ExternalFunctionStatement nod
3535
GenerateSpaceAndFragmentIfNotNull(node.Name);
3636
if (node.Parameters != null && node.Parameters.Count > 0)
3737
{
38-
GenerateSpace();
39-
GenerateParenthesisedCommaSeparatedList(node.Parameters);
38+
if (_options.MultilineProcedureParametersList)
39+
{
40+
// Opt-in: one parameter per line, indented one level.
41+
ListGenerationOption option = ListGenerationOption.CreateOptionFromFormattingConfig(_options);
42+
GenerateFragmentList(node.Parameters, option);
43+
}
44+
else
45+
{
46+
// Default: unchanged single-line, parenthesized parameter list.
47+
GenerateSpace();
48+
GenerateParenthesisedCommaSeparatedList(node.Parameters);
49+
}
4050
}
4151
if (node.ReturnType != null)
4252
{

‎SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FromClause.cs‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ protected void GenerateFromClause(FromClause fromClause, AlignmentPoint clauseBo
2929

3030
GenerateKeyword(TSqlTokenType.From);
3131

32-
MarkClauseBodyAlignmentWhenNecessary(_options.NewLineBeforeFromClause, clauseBody);
33-
34-
GenerateSpace();
32+
if (!GenerateClauseBodyStart(_options.NewLineBeforeFromClause, clauseBody))
33+
{
34+
GenerateSpace();
35+
}
3536

3637
AlignmentPoint fromItems = new AlignmentPoint();
3738
MarkAndPushAlignmentPoint(fromItems);

‎SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FunctionStatementBody.cs‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,7 @@ protected void GenerateFunctionStatementBody(FunctionStatementBody node)
5252
GenerateSpaceAndFragmentIfNotNull(node.Name);
5353

5454
// parameters
55-
NewLine();
56-
GenerateParenthesisedCommaSeparatedList(node.Parameters);
57-
if (node.Parameters == null || node.Parameters.Count == 0)
58-
{
59-
GenerateSymbol(TSqlTokenType.LeftParenthesis);
60-
GenerateSpaceAndSymbol(TSqlTokenType.RightParenthesis);
61-
}
55+
GenerateProcedureOrFunctionParameters(node.Parameters, parenthesized: true);
6256

6357
// RETURNS
6458
NewLine();

‎SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.GroupByClause.cs‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ public override void ExplicitVisit(GroupByClause node)
2222
}
2323

2424
AlignmentPoint clauseBody = GetAlignmentPointForFragment(node, ClauseBody);
25-
MarkClauseBodyAlignmentWhenNecessary(_options.NewLineBeforeGroupByClause, clauseBody);
26-
27-
GenerateSpace();
25+
if (!GenerateClauseBodyStart(_options.NewLineBeforeGroupByClause, clauseBody))
26+
{
27+
GenerateSpace();
28+
}
2829
GenerateCommaSeparatedList(node.GroupingSpecifications);
2930

3031
if (node.GroupByOption != GroupByOption.None)

‎SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.HavingClause.cs‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,14 @@ public override void ExplicitVisit(HavingClause node)
1717
GenerateKeyword(TSqlTokenType.Having);
1818

1919
AlignmentPoint clauseBody = GetAlignmentPointForFragment(node, ClauseBody);
20-
MarkClauseBodyAlignmentWhenNecessary(_options.NewLineBeforeHavingClause, clauseBody);
21-
22-
GenerateSpaceAndFragmentIfNotNull(node.SearchCondition);
20+
if (GenerateClauseBodyStart(_options.NewLineBeforeHavingClause, clauseBody))
21+
{
22+
GenerateFragmentIfNotNull(node.SearchCondition);
23+
}
24+
else
25+
{
26+
GenerateSpaceAndFragmentIfNotNull(node.SearchCondition);
27+
}
2328

2429
PopAlignmentPoint();
2530
}

‎SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.InPredicate.cs‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,17 @@ public override void ExplicitVisit(InPredicate node)
2222

2323
if (node.Values.Count > 0)
2424
{
25-
GenerateSpace();
26-
GenerateParenthesisedCommaSeparatedList(node.Values);
25+
if (_options.MultilineInValuesList)
26+
{
27+
ListGenerationOption option = ListGenerationOption.CreateOptionFromFormattingConfig(_options);
28+
29+
GenerateFragmentList(node.Values, option);
30+
}
31+
else
32+
{
33+
GenerateSpace();
34+
GenerateParenthesisedCommaSeparatedList(node.Values);
35+
}
2736
}
2837

2938
GenerateSpaceAndFragmentIfNotNull(node.Subquery);

‎SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.InsertStatement.cs‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,16 @@ public override void ExplicitVisit(InsertSpecification node)
6666
if (node.Columns.Count > 0)
6767
{
6868
MarkInsertColumnsAlignmentPointWhenNecessary(insertColumns);
69-
GenerateSpace();
70-
GenerateParenthesisedCommaSeparatedList(node.Columns);
69+
if (_options.MultilineInsertTargetsList)
70+
{
71+
ListGenerationOption option = ListGenerationOption.CreateOptionFromFormattingConfig(_options);
72+
GenerateFragmentList(node.Columns, option);
73+
}
74+
else
75+
{
76+
GenerateSpace();
77+
GenerateParenthesisedCommaSeparatedList(node.Columns);
78+
}
7179
}
7280
}
7381

0 commit comments

Comments
 (0)