@@ -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),
0 commit comments