@@ -236,25 +236,7 @@ protected string GeneratePropertyAssignment(DtoProperty property, int indents)
236236 // For nested structure cases
237237 if ( property . NestedStructure is not null )
238238 {
239- // General approach: Replace any anonymous type creation in the expression with the DTO
240- // This works for direct anonymous types, ternary operators, and any other expression structure
241- var anonymousCreation = syntax
242- . DescendantNodesAndSelf ( )
243- . OfType < AnonymousObjectCreationExpressionSyntax > ( )
244- . FirstOrDefault ( ) ;
245-
246- if ( anonymousCreation != null )
247- {
248- // Convert the expression by replacing the anonymous type with the DTO
249- return ConvertExpressionWithAnonymousTypeToDto (
250- syntax ,
251- anonymousCreation ,
252- property . NestedStructure ,
253- indents
254- ) ;
255- }
256-
257- // Check if this contains SelectMany
239+ // Check if this contains SelectMany first (it's more specific)
258240 if ( RoslynTypeHelper . ContainsSelectManyInvocation ( syntax ) )
259241 {
260242 // For nested SelectMany (collection flattening) case
@@ -275,19 +257,44 @@ protected string GeneratePropertyAssignment(DtoProperty property, int indents)
275257 return convertedSelectMany ;
276258 }
277259
278- // For nested Select (collection) case
279- var convertedSelect = ConvertNestedSelectWithRoslyn (
280- syntax ,
281- property . NestedStructure ,
282- indents
283- ) ;
284- // Debug: Check if conversion was performed correctly
285- if ( convertedSelect == expression && RoslynTypeHelper . ContainsSelectInvocation ( syntax ) )
260+ // Check if this contains Select - use dedicated formatting for better output
261+ if ( RoslynTypeHelper . ContainsSelectInvocation ( syntax ) )
286262 {
287- // If conversion was not performed, leave the original expression as a comment
288- return $ "{ convertedSelect } /* CONVERSION FAILED: { property . Name } */";
263+ // For nested Select (collection) case - handles both anonymous and named types
264+ var convertedSelect = ConvertNestedSelectWithRoslyn (
265+ syntax ,
266+ property . NestedStructure ,
267+ indents
268+ ) ;
269+ // Debug: Check if conversion was performed correctly
270+ if ( convertedSelect == expression )
271+ {
272+ // If conversion was not performed, leave the original expression as a comment
273+ return $ "{ convertedSelect } /* CONVERSION FAILED: { property . Name } */";
274+ }
275+ return convertedSelect ;
276+ }
277+
278+ // For other cases with anonymous types (e.g., ternary operators, direct anonymous types)
279+ // Replace any anonymous type creation in the expression with the DTO
280+ var anonymousCreation = syntax
281+ . DescendantNodesAndSelf ( )
282+ . OfType < AnonymousObjectCreationExpressionSyntax > ( )
283+ . FirstOrDefault ( ) ;
284+
285+ if ( anonymousCreation != null )
286+ {
287+ // Convert the expression by replacing the anonymous type with the DTO
288+ return ConvertExpressionWithAnonymousTypeToDto (
289+ syntax ,
290+ anonymousCreation ,
291+ property . NestedStructure ,
292+ indents
293+ ) ;
289294 }
290- return convertedSelect ;
295+
296+ // Fallback: return the original expression
297+ return expression ;
291298 }
292299 // If nullable operator is used, convert to explicit null check
293300 if (
@@ -426,24 +433,29 @@ protected string ConvertExpressionWithAnonymousTypeToDto(
426433 int indents
427434 )
428435 {
436+ var spaces = CodeFormatter . IndentSpaces ( indents ) ;
437+ var innerSpaces = CodeFormatter . IndentSpaces ( indents + CodeFormatter . IndentSize ) ;
429438 var nestedClassName = GetClassName ( nestedStructure ) ;
430439 var nestedDtoName = string . IsNullOrEmpty ( nestedClassName )
431440 ? ""
432441 : GetNestedDtoFullName ( nestedClassName ) ;
433442
434- // Generate the DTO object creation to replace the anonymous type
435- // We need to preserve indentation based on where the anonymous type appears
443+ // Generate the DTO object creation to replace the anonymous type with proper formatting
436444 var propertyAssignments = new List < string > ( ) ;
437445 foreach ( var prop in nestedStructure . Properties )
438446 {
439- var assignment = GeneratePropertyAssignment ( prop , 0 ) ;
440- propertyAssignments . Add ( $ " { prop . Name } = { assignment } ") ;
447+ var assignment = GeneratePropertyAssignment ( prop , indents + CodeFormatter . IndentSize ) ;
448+ propertyAssignments . Add ( $ "{ innerSpaces } { prop . Name } = { assignment } ") ;
441449 }
442450 var propertiesCode = string . Join ( $ ",{ CodeFormatter . DefaultNewLine } ", propertyAssignments ) ;
443451
444- // Build the DTO creation as a compact single-line or multi-line depending on complexity
445- var dtoCreation =
446- $ "new { nestedDtoName } { CodeFormatter . DefaultNewLine } {{{CodeFormatter.DefaultNewLine}{ propertiesCode } { CodeFormatter . DefaultNewLine } }}";
452+ // Build the DTO creation with proper formatting
453+ var dtoCreation = $$ """
454+ new {{ nestedDtoName }}
455+ {{ spaces }} {
456+ {{ propertiesCode }}
457+ {{ spaces }} }
458+ """ ;
447459
448460 // Remove comments from syntax before processing
449461 var cleanSyntax = RemoveComments ( syntax ) ;
@@ -469,26 +481,29 @@ int indents
469481 )
470482 {
471483 var spaces = CodeFormatter . IndentSpaces ( indents ) ;
484+ var innerSpaces = CodeFormatter . IndentSpaces ( indents + CodeFormatter . IndentSize ) ;
472485 var nestedClassName = GetClassName ( nestedStructure ) ;
473486 // For anonymous types (empty class name), don't use namespace qualification
474487 var nestedDtoName = string . IsNullOrEmpty ( nestedClassName )
475488 ? ""
476489 : GetNestedDtoFullName ( nestedClassName ) ;
477490
478- // Generate property assignments for nested DTO
491+ // Generate property assignments for nested DTO with proper formatting
479492 var propertyAssignments = new List < string > ( ) ;
480493 foreach ( var prop in nestedStructure . Properties )
481494 {
482- var assignment = GeneratePropertyAssignment ( prop , indents + CodeFormatter . IndentSize ) ;
483- propertyAssignments . Add (
484- $ " { spaces } { CodeFormatter . Indent ( 1 ) } { prop . Name } = { assignment } ,"
495+ var assignment = GeneratePropertyAssignment (
496+ prop ,
497+ indents + CodeFormatter . IndentSize * 2
485498 ) ;
499+ propertyAssignments . Add ( $ "{ innerSpaces } { prop . Name } = { assignment } ") ;
486500 }
487- var propertiesCode = string . Join ( CodeFormatter . DefaultNewLine , propertyAssignments ) ;
501+ var propertiesCode = string . Join ( $ ", { CodeFormatter . DefaultNewLine } " , propertyAssignments ) ;
488502
489- // Build the new DTO object creation expression
503+ // Build the new DTO object creation expression with proper formatting
490504 var code = $$ """
491- new {{ nestedDtoName }} {
505+ new {{ nestedDtoName }}
506+ {{ spaces }} {
492507 {{ propertiesCode }}
493508 {{ spaces }} }
494509 """ ;
@@ -505,6 +520,7 @@ int indents
505520 )
506521 {
507522 var spaces = CodeFormatter . IndentSpaces ( indents ) ;
523+ var innerSpaces = CodeFormatter . IndentSpaces ( indents + CodeFormatter . IndentSize ) ;
508524 var nestedClassName = GetClassName ( nestedStructure ) ;
509525 // For anonymous types (empty class name), don't use namespace qualification
510526 var nestedDtoName = string . IsNullOrEmpty ( nestedClassName )
@@ -535,18 +551,25 @@ int indents
535551 "."
536552 ) ;
537553
538- // Generate property assignments for nested DTO
554+ // Generate property assignments for nested DTO with proper formatting
555+ // Properties should be indented two levels from the base (one for Select block, one for properties)
556+ var propertyIndentSpaces = CodeFormatter . IndentSpaces ( indents + CodeFormatter . IndentSize * 2 ) ;
539557 var propertyAssignments = new List < string > ( ) ;
540558 foreach ( var prop in nestedStructure . Properties )
541559 {
542- var assignment = GeneratePropertyAssignment ( prop , indents + CodeFormatter . IndentSize ) ;
543- propertyAssignments . Add (
544- $ " { spaces } { CodeFormatter . Indent ( 1 ) } { prop . Name } = { assignment } ,"
560+ var assignment = GeneratePropertyAssignment (
561+ prop ,
562+ indents + CodeFormatter . IndentSize * 2
545563 ) ;
564+ propertyAssignments . Add ( $ "{ propertyIndentSpaces } { prop . Name } = { assignment } ") ;
546565 }
547- var propertiesCode = string . Join ( CodeFormatter . DefaultNewLine , propertyAssignments ) ;
566+ var propertiesCode = string . Join ( $ ", { CodeFormatter . DefaultNewLine } " , propertyAssignments ) ;
548567
549- // Build the Select expression
568+ // Format chained methods with proper indentation (one level from base)
569+ var formattedChainedMethods = FormatChainedMethods ( chainedMethods , innerSpaces ) ;
570+
571+ // Build the Select expression with proper formatting
572+ // The .Select, {, }, and chained methods should all be indented one level from the property assignment
550573 if ( hasNullableAccess )
551574 {
552575 // Determine default value
@@ -559,23 +582,78 @@ int indents
559582 ) ;
560583
561584 var code = $$ """
562- {{ baseExpression }} != null ? {{ baseExpression }} .Select({{ paramName }} => new {{ nestedDtoName }} {
585+ {{ baseExpression }} != null ? {{ baseExpression }}
586+ {{ innerSpaces }} .Select({{ paramName }} => new {{ nestedDtoName }}
587+ {{ innerSpaces }} {
563588 {{ propertiesCode }}
564- {{ spaces }} }){{ chainedMethods }} : {{ defaultValue }}
589+ {{ innerSpaces }} }){{ formattedChainedMethods }} : {{ defaultValue }}
565590 """ ;
566591 return code ;
567592 }
568593 else
569594 {
570595 var code = $$ """
571- {{ baseExpression }} .Select({{ paramName }} => new {{ nestedDtoName }} {
596+ {{ baseExpression }}
597+ {{ innerSpaces }} .Select({{ paramName }} => new {{ nestedDtoName }}
598+ {{ innerSpaces }} {
572599 {{ propertiesCode }}
573- {{ spaces }} }){{ chainedMethods }}
600+ {{ innerSpaces }} }){{ formattedChainedMethods }}
574601 """ ;
575602 return code ;
576603 }
577604 }
578605
606+ /// <summary>
607+ /// Formats chained method calls (like .ToList()) with proper indentation
608+ /// </summary>
609+ private static string FormatChainedMethods ( string chainedMethods , string spaces )
610+ {
611+ if ( string . IsNullOrEmpty ( chainedMethods ) )
612+ return "" ;
613+
614+ // Normalize whitespace from chained method calls
615+ var normalized = System . Text . RegularExpressions . Regex . Replace (
616+ chainedMethods . Trim ( ) ,
617+ @"\s+" ,
618+ ""
619+ ) ;
620+
621+ // Each method call should be on a new line with proper indentation
622+ var result = new StringBuilder ( ) ;
623+ var currentMethod = new StringBuilder ( ) ;
624+ var parenDepth = 0 ;
625+
626+ foreach ( var c in normalized )
627+ {
628+ currentMethod . Append ( c ) ;
629+
630+ if ( c == '(' )
631+ parenDepth ++ ;
632+ else if ( c == ')' )
633+ {
634+ parenDepth -- ;
635+ if ( parenDepth == 0 )
636+ {
637+ // Complete method call with parentheses
638+ result . Append ( CodeFormatter . DefaultNewLine ) ;
639+ result . Append ( spaces ) ;
640+ result . Append ( currentMethod ) ;
641+ currentMethod . Clear ( ) ;
642+ }
643+ }
644+ }
645+
646+ // Handle any remaining content (e.g., property access without parentheses like .Count)
647+ if ( currentMethod . Length > 0 )
648+ {
649+ result . Append ( CodeFormatter . DefaultNewLine ) ;
650+ result . Append ( spaces ) ;
651+ result . Append ( currentMethod ) ;
652+ }
653+
654+ return result . ToString ( ) ;
655+ }
656+
579657 /// <summary>
580658 /// Converts nested SelectMany expressions using Roslyn syntax analysis
581659 /// </summary>
@@ -586,6 +664,7 @@ int indents
586664 )
587665 {
588666 var spaces = CodeFormatter . IndentSpaces ( indents ) ;
667+ var innerSpaces = CodeFormatter . IndentSpaces ( indents + CodeFormatter . IndentSize ) ;
589668
590669 // Use Roslyn to extract SelectMany information
591670 var selectManyInfo = ExtractSelectManyInfoFromSyntax ( syntax ) ;
@@ -619,21 +698,25 @@ int indents
619698 ? ""
620699 : GetNestedDtoFullName ( nestedClassName ) ;
621700
622- // Generate property assignments for nested DTO
701+ // Generate property assignments for nested DTO with proper formatting
702+ // Properties should be indented two levels from the base (one for SelectMany block, one for properties)
703+ var propertyIndentSpaces = CodeFormatter . IndentSpaces ( indents + CodeFormatter . IndentSize * 2 ) ;
623704 var propertyAssignments = new List < string > ( ) ;
624705 foreach ( var prop in nestedStructure . Properties )
625706 {
626707 var assignment = GeneratePropertyAssignment (
627708 prop ,
628- indents + CodeFormatter . IndentSize
629- ) ;
630- propertyAssignments . Add (
631- $ "{ spaces } { CodeFormatter . Indent ( 1 ) } { prop . Name } = { assignment } ,"
709+ indents + CodeFormatter . IndentSize * 2
632710 ) ;
711+ propertyAssignments . Add ( $ "{ propertyIndentSpaces } { prop . Name } = { assignment } ") ;
633712 }
634- var propertiesCode = string . Join ( CodeFormatter . DefaultNewLine , propertyAssignments ) ;
713+ var propertiesCode = string . Join ( $ ",{ CodeFormatter . DefaultNewLine } ", propertyAssignments ) ;
714+
715+ // Format chained methods with proper indentation (one level from base)
716+ var formattedChainedMethods = FormatChainedMethods ( chainedMethods , innerSpaces ) ;
635717
636- // Build the SelectMany expression with projection
718+ // Build the SelectMany expression with projection and proper formatting
719+ // The .SelectMany, {, }, and chained methods should all be indented one level from the property assignment
637720 if ( hasNullableAccess )
638721 {
639722 var defaultValue =
@@ -645,18 +728,22 @@ int indents
645728 ) ;
646729
647730 var code = $$ """
648- {{ baseExpression }} != null ? {{ baseExpression }} .SelectMany({{ paramName }} => new {{ nestedDtoName }} {
731+ {{ baseExpression }} != null ? {{ baseExpression }}
732+ {{ innerSpaces }} .SelectMany({{ paramName }} => new {{ nestedDtoName }}
733+ {{ innerSpaces }} {
649734 {{ propertiesCode }}
650- {{ spaces }} }){{ chainedMethods }} : {{ defaultValue }}
735+ {{ innerSpaces }} }){{ formattedChainedMethods }} : {{ defaultValue }}
651736 """ ;
652737 return code ;
653738 }
654739 else
655740 {
656741 var code = $$ """
657- {{ baseExpression }} .SelectMany({{ paramName }} => new {{ nestedDtoName }} {
742+ {{ baseExpression }}
743+ {{ innerSpaces }} .SelectMany({{ paramName }} => new {{ nestedDtoName }}
744+ {{ innerSpaces }} {
658745 {{ propertiesCode }}
659- {{ spaces }} }){{ chainedMethods }}
746+ {{ innerSpaces }} }){{ formattedChainedMethods }}
660747 """ ;
661748 return code ;
662749 }
0 commit comments